Ben Murdoch | 097c5b2 | 2016-05-18 11:27:45 +0100 | [diff] [blame^] | 1 | # Copyright (c) 2012 Google Inc. All rights reserved. |
| 2 | # Use of this source code is governed by a BSD-style license that can be |
| 3 | # found in the LICENSE file. |
| 4 | |
| 5 | import copy |
| 6 | import ntpath |
| 7 | import os |
| 8 | import posixpath |
| 9 | import re |
| 10 | import subprocess |
| 11 | import sys |
| 12 | |
| 13 | import gyp.common |
| 14 | import gyp.easy_xml as easy_xml |
| 15 | import gyp.generator.ninja as ninja_generator |
| 16 | import gyp.MSVSNew as MSVSNew |
| 17 | import gyp.MSVSProject as MSVSProject |
| 18 | import gyp.MSVSSettings as MSVSSettings |
| 19 | import gyp.MSVSToolFile as MSVSToolFile |
| 20 | import gyp.MSVSUserFile as MSVSUserFile |
| 21 | import gyp.MSVSUtil as MSVSUtil |
| 22 | import gyp.MSVSVersion as MSVSVersion |
| 23 | from gyp.common import GypError |
| 24 | from gyp.common import OrderedSet |
| 25 | |
| 26 | # TODO: Remove once bots are on 2.7, http://crbug.com/241769 |
| 27 | def _import_OrderedDict(): |
| 28 | import collections |
| 29 | try: |
| 30 | return collections.OrderedDict |
| 31 | except AttributeError: |
| 32 | import gyp.ordered_dict |
| 33 | return gyp.ordered_dict.OrderedDict |
| 34 | OrderedDict = _import_OrderedDict() |
| 35 | |
| 36 | |
| 37 | # Regular expression for validating Visual Studio GUIDs. If the GUID |
| 38 | # contains lowercase hex letters, MSVS will be fine. However, |
| 39 | # IncrediBuild BuildConsole will parse the solution file, but then |
| 40 | # silently skip building the target causing hard to track down errors. |
| 41 | # Note that this only happens with the BuildConsole, and does not occur |
| 42 | # if IncrediBuild is executed from inside Visual Studio. This regex |
| 43 | # validates that the string looks like a GUID with all uppercase hex |
| 44 | # letters. |
| 45 | VALID_MSVS_GUID_CHARS = re.compile(r'^[A-F0-9\-]+$') |
| 46 | |
| 47 | |
| 48 | generator_default_variables = { |
| 49 | 'EXECUTABLE_PREFIX': '', |
| 50 | 'EXECUTABLE_SUFFIX': '.exe', |
| 51 | 'STATIC_LIB_PREFIX': '', |
| 52 | 'SHARED_LIB_PREFIX': '', |
| 53 | 'STATIC_LIB_SUFFIX': '.lib', |
| 54 | 'SHARED_LIB_SUFFIX': '.dll', |
| 55 | 'INTERMEDIATE_DIR': '$(IntDir)', |
| 56 | 'SHARED_INTERMEDIATE_DIR': '$(OutDir)obj/global_intermediate', |
| 57 | 'OS': 'win', |
| 58 | 'PRODUCT_DIR': '$(OutDir)', |
| 59 | 'LIB_DIR': '$(OutDir)lib', |
| 60 | 'RULE_INPUT_ROOT': '$(InputName)', |
| 61 | 'RULE_INPUT_DIRNAME': '$(InputDir)', |
| 62 | 'RULE_INPUT_EXT': '$(InputExt)', |
| 63 | 'RULE_INPUT_NAME': '$(InputFileName)', |
| 64 | 'RULE_INPUT_PATH': '$(InputPath)', |
| 65 | 'CONFIGURATION_NAME': '$(ConfigurationName)', |
| 66 | } |
| 67 | |
| 68 | |
| 69 | # The msvs specific sections that hold paths |
| 70 | generator_additional_path_sections = [ |
| 71 | 'msvs_cygwin_dirs', |
| 72 | 'msvs_props', |
| 73 | ] |
| 74 | |
| 75 | |
| 76 | generator_additional_non_configuration_keys = [ |
| 77 | 'msvs_cygwin_dirs', |
| 78 | 'msvs_cygwin_shell', |
| 79 | 'msvs_large_pdb', |
| 80 | 'msvs_shard', |
| 81 | 'msvs_external_builder', |
| 82 | 'msvs_external_builder_out_dir', |
| 83 | 'msvs_external_builder_build_cmd', |
| 84 | 'msvs_external_builder_clean_cmd', |
| 85 | 'msvs_external_builder_clcompile_cmd', |
| 86 | 'msvs_enable_winrt', |
| 87 | 'msvs_requires_importlibrary', |
| 88 | 'msvs_enable_winphone', |
| 89 | 'msvs_application_type_revision', |
| 90 | 'msvs_target_platform_version', |
| 91 | 'msvs_target_platform_minversion', |
| 92 | ] |
| 93 | |
| 94 | generator_filelist_paths = None |
| 95 | |
| 96 | # List of precompiled header related keys. |
| 97 | precomp_keys = [ |
| 98 | 'msvs_precompiled_header', |
| 99 | 'msvs_precompiled_source', |
| 100 | ] |
| 101 | |
| 102 | |
| 103 | cached_username = None |
| 104 | |
| 105 | |
| 106 | cached_domain = None |
| 107 | |
| 108 | |
| 109 | # TODO(gspencer): Switch the os.environ calls to be |
| 110 | # win32api.GetDomainName() and win32api.GetUserName() once the |
| 111 | # python version in depot_tools has been updated to work on Vista |
| 112 | # 64-bit. |
| 113 | def _GetDomainAndUserName(): |
| 114 | if sys.platform not in ('win32', 'cygwin'): |
| 115 | return ('DOMAIN', 'USERNAME') |
| 116 | global cached_username |
| 117 | global cached_domain |
| 118 | if not cached_domain or not cached_username: |
| 119 | domain = os.environ.get('USERDOMAIN') |
| 120 | username = os.environ.get('USERNAME') |
| 121 | if not domain or not username: |
| 122 | call = subprocess.Popen(['net', 'config', 'Workstation'], |
| 123 | stdout=subprocess.PIPE) |
| 124 | config = call.communicate()[0] |
| 125 | username_re = re.compile(r'^User name\s+(\S+)', re.MULTILINE) |
| 126 | username_match = username_re.search(config) |
| 127 | if username_match: |
| 128 | username = username_match.group(1) |
| 129 | domain_re = re.compile(r'^Logon domain\s+(\S+)', re.MULTILINE) |
| 130 | domain_match = domain_re.search(config) |
| 131 | if domain_match: |
| 132 | domain = domain_match.group(1) |
| 133 | cached_domain = domain |
| 134 | cached_username = username |
| 135 | return (cached_domain, cached_username) |
| 136 | |
| 137 | fixpath_prefix = None |
| 138 | |
| 139 | |
| 140 | def _NormalizedSource(source): |
| 141 | """Normalize the path. |
| 142 | |
| 143 | But not if that gets rid of a variable, as this may expand to something |
| 144 | larger than one directory. |
| 145 | |
| 146 | Arguments: |
| 147 | source: The path to be normalize.d |
| 148 | |
| 149 | Returns: |
| 150 | The normalized path. |
| 151 | """ |
| 152 | normalized = os.path.normpath(source) |
| 153 | if source.count('$') == normalized.count('$'): |
| 154 | source = normalized |
| 155 | return source |
| 156 | |
| 157 | |
| 158 | def _FixPath(path): |
| 159 | """Convert paths to a form that will make sense in a vcproj file. |
| 160 | |
| 161 | Arguments: |
| 162 | path: The path to convert, may contain / etc. |
| 163 | Returns: |
| 164 | The path with all slashes made into backslashes. |
| 165 | """ |
| 166 | if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$': |
| 167 | path = os.path.join(fixpath_prefix, path) |
| 168 | path = path.replace('/', '\\') |
| 169 | path = _NormalizedSource(path) |
| 170 | if path and path[-1] == '\\': |
| 171 | path = path[:-1] |
| 172 | return path |
| 173 | |
| 174 | |
| 175 | def _FixPaths(paths): |
| 176 | """Fix each of the paths of the list.""" |
| 177 | return [_FixPath(i) for i in paths] |
| 178 | |
| 179 | |
| 180 | def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None, |
| 181 | list_excluded=True, msvs_version=None): |
| 182 | """Converts a list split source file paths into a vcproj folder hierarchy. |
| 183 | |
| 184 | Arguments: |
| 185 | sources: A list of source file paths split. |
| 186 | prefix: A list of source file path layers meant to apply to each of sources. |
| 187 | excluded: A set of excluded files. |
| 188 | msvs_version: A MSVSVersion object. |
| 189 | |
| 190 | Returns: |
| 191 | A hierarchy of filenames and MSVSProject.Filter objects that matches the |
| 192 | layout of the source tree. |
| 193 | For example: |
| 194 | _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']], |
| 195 | prefix=['joe']) |
| 196 | --> |
| 197 | [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']), |
| 198 | MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])] |
| 199 | """ |
| 200 | if not prefix: prefix = [] |
| 201 | result = [] |
| 202 | excluded_result = [] |
| 203 | folders = OrderedDict() |
| 204 | # Gather files into the final result, excluded, or folders. |
| 205 | for s in sources: |
| 206 | if len(s) == 1: |
| 207 | filename = _NormalizedSource('\\'.join(prefix + s)) |
| 208 | if filename in excluded: |
| 209 | excluded_result.append(filename) |
| 210 | else: |
| 211 | result.append(filename) |
| 212 | elif msvs_version and not msvs_version.UsesVcxproj(): |
| 213 | # For MSVS 2008 and earlier, we need to process all files before walking |
| 214 | # the sub folders. |
| 215 | if not folders.get(s[0]): |
| 216 | folders[s[0]] = [] |
| 217 | folders[s[0]].append(s[1:]) |
| 218 | else: |
| 219 | contents = _ConvertSourcesToFilterHierarchy([s[1:]], prefix + [s[0]], |
| 220 | excluded=excluded, |
| 221 | list_excluded=list_excluded, |
| 222 | msvs_version=msvs_version) |
| 223 | contents = MSVSProject.Filter(s[0], contents=contents) |
| 224 | result.append(contents) |
| 225 | # Add a folder for excluded files. |
| 226 | if excluded_result and list_excluded: |
| 227 | excluded_folder = MSVSProject.Filter('_excluded_files', |
| 228 | contents=excluded_result) |
| 229 | result.append(excluded_folder) |
| 230 | |
| 231 | if msvs_version and msvs_version.UsesVcxproj(): |
| 232 | return result |
| 233 | |
| 234 | # Populate all the folders. |
| 235 | for f in folders: |
| 236 | contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f], |
| 237 | excluded=excluded, |
| 238 | list_excluded=list_excluded, |
| 239 | msvs_version=msvs_version) |
| 240 | contents = MSVSProject.Filter(f, contents=contents) |
| 241 | result.append(contents) |
| 242 | return result |
| 243 | |
| 244 | |
| 245 | def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False): |
| 246 | if not value: return |
| 247 | _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset) |
| 248 | |
| 249 | |
| 250 | def _ToolSetOrAppend(tools, tool_name, setting, value, only_if_unset=False): |
| 251 | # TODO(bradnelson): ugly hack, fix this more generally!!! |
| 252 | if 'Directories' in setting or 'Dependencies' in setting: |
| 253 | if type(value) == str: |
| 254 | value = value.replace('/', '\\') |
| 255 | else: |
| 256 | value = [i.replace('/', '\\') for i in value] |
| 257 | if not tools.get(tool_name): |
| 258 | tools[tool_name] = dict() |
| 259 | tool = tools[tool_name] |
| 260 | if 'CompileAsWinRT' == setting: |
| 261 | return |
| 262 | if tool.get(setting): |
| 263 | if only_if_unset: return |
| 264 | if type(tool[setting]) == list and type(value) == list: |
| 265 | tool[setting] += value |
| 266 | else: |
| 267 | raise TypeError( |
| 268 | 'Appending "%s" to a non-list setting "%s" for tool "%s" is ' |
| 269 | 'not allowed, previous value: %s' % ( |
| 270 | value, setting, tool_name, str(tool[setting]))) |
| 271 | else: |
| 272 | tool[setting] = value |
| 273 | |
| 274 | |
| 275 | def _ConfigPlatform(config_data): |
| 276 | return config_data.get('msvs_configuration_platform', 'Win32') |
| 277 | |
| 278 | |
| 279 | def _ConfigBaseName(config_name, platform_name): |
| 280 | if config_name.endswith('_' + platform_name): |
| 281 | return config_name[0:-len(platform_name) - 1] |
| 282 | else: |
| 283 | return config_name |
| 284 | |
| 285 | |
| 286 | def _ConfigFullName(config_name, config_data): |
| 287 | platform_name = _ConfigPlatform(config_data) |
| 288 | return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name) |
| 289 | |
| 290 | |
| 291 | def _ConfigWindowsTargetPlatformVersion(config_data): |
| 292 | ver = config_data.get('msvs_windows_sdk_version') |
| 293 | |
| 294 | for key in [r'HKLM\Software\Microsoft\Microsoft SDKs\Windows\%s', |
| 295 | r'HKLM\Software\Wow6432Node\Microsoft\Microsoft SDKs\Windows\%s']: |
| 296 | sdk_dir = MSVSVersion._RegistryGetValue(key % ver, 'InstallationFolder') |
| 297 | if not sdk_dir: |
| 298 | continue |
| 299 | version = MSVSVersion._RegistryGetValue(key % ver, 'ProductVersion') or '' |
| 300 | # Find a matching entry in sdk_dir\include. |
| 301 | names = sorted([x for x in os.listdir(r'%s\include' % sdk_dir) |
| 302 | if x.startswith(version)], reverse=True) |
| 303 | return names[0] |
| 304 | |
| 305 | |
| 306 | def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path, |
| 307 | quote_cmd, do_setup_env): |
| 308 | |
| 309 | if [x for x in cmd if '$(InputDir)' in x]: |
| 310 | input_dir_preamble = ( |
| 311 | 'set INPUTDIR=$(InputDir)\n' |
| 312 | 'if NOT DEFINED INPUTDIR set INPUTDIR=.\\\n' |
| 313 | 'set INPUTDIR=%INPUTDIR:~0,-1%\n' |
| 314 | ) |
| 315 | else: |
| 316 | input_dir_preamble = '' |
| 317 | |
| 318 | if cygwin_shell: |
| 319 | # Find path to cygwin. |
| 320 | cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0]) |
| 321 | # Prepare command. |
| 322 | direct_cmd = cmd |
| 323 | direct_cmd = [i.replace('$(IntDir)', |
| 324 | '`cygpath -m "${INTDIR}"`') for i in direct_cmd] |
| 325 | direct_cmd = [i.replace('$(OutDir)', |
| 326 | '`cygpath -m "${OUTDIR}"`') for i in direct_cmd] |
| 327 | direct_cmd = [i.replace('$(InputDir)', |
| 328 | '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd] |
| 329 | if has_input_path: |
| 330 | direct_cmd = [i.replace('$(InputPath)', |
| 331 | '`cygpath -m "${INPUTPATH}"`') |
| 332 | for i in direct_cmd] |
| 333 | direct_cmd = ['\\"%s\\"' % i.replace('"', '\\\\\\"') for i in direct_cmd] |
| 334 | # direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd) |
| 335 | direct_cmd = ' '.join(direct_cmd) |
| 336 | # TODO(quote): regularize quoting path names throughout the module |
| 337 | cmd = '' |
| 338 | if do_setup_env: |
| 339 | cmd += 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && ' |
| 340 | cmd += 'set CYGWIN=nontsec&& ' |
| 341 | if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0: |
| 342 | cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& ' |
| 343 | if direct_cmd.find('INTDIR') >= 0: |
| 344 | cmd += 'set INTDIR=$(IntDir)&& ' |
| 345 | if direct_cmd.find('OUTDIR') >= 0: |
| 346 | cmd += 'set OUTDIR=$(OutDir)&& ' |
| 347 | if has_input_path and direct_cmd.find('INPUTPATH') >= 0: |
| 348 | cmd += 'set INPUTPATH=$(InputPath) && ' |
| 349 | cmd += 'bash -c "%(cmd)s"' |
| 350 | cmd = cmd % {'cygwin_dir': cygwin_dir, |
| 351 | 'cmd': direct_cmd} |
| 352 | return input_dir_preamble + cmd |
| 353 | else: |
| 354 | # Convert cat --> type to mimic unix. |
| 355 | if cmd[0] == 'cat': |
| 356 | command = ['type'] |
| 357 | else: |
| 358 | command = [cmd[0].replace('/', '\\')] |
| 359 | # Add call before command to ensure that commands can be tied together one |
| 360 | # after the other without aborting in Incredibuild, since IB makes a bat |
| 361 | # file out of the raw command string, and some commands (like python) are |
| 362 | # actually batch files themselves. |
| 363 | command.insert(0, 'call') |
| 364 | # Fix the paths |
| 365 | # TODO(quote): This is a really ugly heuristic, and will miss path fixing |
| 366 | # for arguments like "--arg=path" or "/opt:path". |
| 367 | # If the argument starts with a slash or dash, it's probably a command line |
| 368 | # switch |
| 369 | arguments = [i if (i[:1] in "/-") else _FixPath(i) for i in cmd[1:]] |
| 370 | arguments = [i.replace('$(InputDir)', '%INPUTDIR%') for i in arguments] |
| 371 | arguments = [MSVSSettings.FixVCMacroSlashes(i) for i in arguments] |
| 372 | if quote_cmd: |
| 373 | # Support a mode for using cmd directly. |
| 374 | # Convert any paths to native form (first element is used directly). |
| 375 | # TODO(quote): regularize quoting path names throughout the module |
| 376 | arguments = ['"%s"' % i for i in arguments] |
| 377 | # Collapse into a single command. |
| 378 | return input_dir_preamble + ' '.join(command + arguments) |
| 379 | |
| 380 | |
| 381 | def _BuildCommandLineForRule(spec, rule, has_input_path, do_setup_env): |
| 382 | # Currently this weird argument munging is used to duplicate the way a |
| 383 | # python script would need to be run as part of the chrome tree. |
| 384 | # Eventually we should add some sort of rule_default option to set this |
| 385 | # per project. For now the behavior chrome needs is the default. |
| 386 | mcs = rule.get('msvs_cygwin_shell') |
| 387 | if mcs is None: |
| 388 | mcs = int(spec.get('msvs_cygwin_shell', 1)) |
| 389 | elif isinstance(mcs, str): |
| 390 | mcs = int(mcs) |
| 391 | quote_cmd = int(rule.get('msvs_quote_cmd', 1)) |
| 392 | return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path, |
| 393 | quote_cmd, do_setup_env=do_setup_env) |
| 394 | |
| 395 | |
| 396 | def _AddActionStep(actions_dict, inputs, outputs, description, command): |
| 397 | """Merge action into an existing list of actions. |
| 398 | |
| 399 | Care must be taken so that actions which have overlapping inputs either don't |
| 400 | get assigned to the same input, or get collapsed into one. |
| 401 | |
| 402 | Arguments: |
| 403 | actions_dict: dictionary keyed on input name, which maps to a list of |
| 404 | dicts describing the actions attached to that input file. |
| 405 | inputs: list of inputs |
| 406 | outputs: list of outputs |
| 407 | description: description of the action |
| 408 | command: command line to execute |
| 409 | """ |
| 410 | # Require there to be at least one input (call sites will ensure this). |
| 411 | assert inputs |
| 412 | |
| 413 | action = { |
| 414 | 'inputs': inputs, |
| 415 | 'outputs': outputs, |
| 416 | 'description': description, |
| 417 | 'command': command, |
| 418 | } |
| 419 | |
| 420 | # Pick where to stick this action. |
| 421 | # While less than optimal in terms of build time, attach them to the first |
| 422 | # input for now. |
| 423 | chosen_input = inputs[0] |
| 424 | |
| 425 | # Add it there. |
| 426 | if chosen_input not in actions_dict: |
| 427 | actions_dict[chosen_input] = [] |
| 428 | actions_dict[chosen_input].append(action) |
| 429 | |
| 430 | |
| 431 | def _AddCustomBuildToolForMSVS(p, spec, primary_input, |
| 432 | inputs, outputs, description, cmd): |
| 433 | """Add a custom build tool to execute something. |
| 434 | |
| 435 | Arguments: |
| 436 | p: the target project |
| 437 | spec: the target project dict |
| 438 | primary_input: input file to attach the build tool to |
| 439 | inputs: list of inputs |
| 440 | outputs: list of outputs |
| 441 | description: description of the action |
| 442 | cmd: command line to execute |
| 443 | """ |
| 444 | inputs = _FixPaths(inputs) |
| 445 | outputs = _FixPaths(outputs) |
| 446 | tool = MSVSProject.Tool( |
| 447 | 'VCCustomBuildTool', |
| 448 | {'Description': description, |
| 449 | 'AdditionalDependencies': ';'.join(inputs), |
| 450 | 'Outputs': ';'.join(outputs), |
| 451 | 'CommandLine': cmd, |
| 452 | }) |
| 453 | # Add to the properties of primary input for each config. |
| 454 | for config_name, c_data in spec['configurations'].iteritems(): |
| 455 | p.AddFileConfig(_FixPath(primary_input), |
| 456 | _ConfigFullName(config_name, c_data), tools=[tool]) |
| 457 | |
| 458 | |
| 459 | def _AddAccumulatedActionsToMSVS(p, spec, actions_dict): |
| 460 | """Add actions accumulated into an actions_dict, merging as needed. |
| 461 | |
| 462 | Arguments: |
| 463 | p: the target project |
| 464 | spec: the target project dict |
| 465 | actions_dict: dictionary keyed on input name, which maps to a list of |
| 466 | dicts describing the actions attached to that input file. |
| 467 | """ |
| 468 | for primary_input in actions_dict: |
| 469 | inputs = OrderedSet() |
| 470 | outputs = OrderedSet() |
| 471 | descriptions = [] |
| 472 | commands = [] |
| 473 | for action in actions_dict[primary_input]: |
| 474 | inputs.update(OrderedSet(action['inputs'])) |
| 475 | outputs.update(OrderedSet(action['outputs'])) |
| 476 | descriptions.append(action['description']) |
| 477 | commands.append(action['command']) |
| 478 | # Add the custom build step for one input file. |
| 479 | description = ', and also '.join(descriptions) |
| 480 | command = '\r\n'.join(commands) |
| 481 | _AddCustomBuildToolForMSVS(p, spec, |
| 482 | primary_input=primary_input, |
| 483 | inputs=inputs, |
| 484 | outputs=outputs, |
| 485 | description=description, |
| 486 | cmd=command) |
| 487 | |
| 488 | |
| 489 | def _RuleExpandPath(path, input_file): |
| 490 | """Given the input file to which a rule applied, string substitute a path. |
| 491 | |
| 492 | Arguments: |
| 493 | path: a path to string expand |
| 494 | input_file: the file to which the rule applied. |
| 495 | Returns: |
| 496 | The string substituted path. |
| 497 | """ |
| 498 | path = path.replace('$(InputName)', |
| 499 | os.path.splitext(os.path.split(input_file)[1])[0]) |
| 500 | path = path.replace('$(InputDir)', os.path.dirname(input_file)) |
| 501 | path = path.replace('$(InputExt)', |
| 502 | os.path.splitext(os.path.split(input_file)[1])[1]) |
| 503 | path = path.replace('$(InputFileName)', os.path.split(input_file)[1]) |
| 504 | path = path.replace('$(InputPath)', input_file) |
| 505 | return path |
| 506 | |
| 507 | |
| 508 | def _FindRuleTriggerFiles(rule, sources): |
| 509 | """Find the list of files which a particular rule applies to. |
| 510 | |
| 511 | Arguments: |
| 512 | rule: the rule in question |
| 513 | sources: the set of all known source files for this project |
| 514 | Returns: |
| 515 | The list of sources that trigger a particular rule. |
| 516 | """ |
| 517 | return rule.get('rule_sources', []) |
| 518 | |
| 519 | |
| 520 | def _RuleInputsAndOutputs(rule, trigger_file): |
| 521 | """Find the inputs and outputs generated by a rule. |
| 522 | |
| 523 | Arguments: |
| 524 | rule: the rule in question. |
| 525 | trigger_file: the main trigger for this rule. |
| 526 | Returns: |
| 527 | The pair of (inputs, outputs) involved in this rule. |
| 528 | """ |
| 529 | raw_inputs = _FixPaths(rule.get('inputs', [])) |
| 530 | raw_outputs = _FixPaths(rule.get('outputs', [])) |
| 531 | inputs = OrderedSet() |
| 532 | outputs = OrderedSet() |
| 533 | inputs.add(trigger_file) |
| 534 | for i in raw_inputs: |
| 535 | inputs.add(_RuleExpandPath(i, trigger_file)) |
| 536 | for o in raw_outputs: |
| 537 | outputs.add(_RuleExpandPath(o, trigger_file)) |
| 538 | return (inputs, outputs) |
| 539 | |
| 540 | |
| 541 | def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options): |
| 542 | """Generate a native rules file. |
| 543 | |
| 544 | Arguments: |
| 545 | p: the target project |
| 546 | rules: the set of rules to include |
| 547 | output_dir: the directory in which the project/gyp resides |
| 548 | spec: the project dict |
| 549 | options: global generator options |
| 550 | """ |
| 551 | rules_filename = '%s%s.rules' % (spec['target_name'], |
| 552 | options.suffix) |
| 553 | rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename), |
| 554 | spec['target_name']) |
| 555 | # Add each rule. |
| 556 | for r in rules: |
| 557 | rule_name = r['rule_name'] |
| 558 | rule_ext = r['extension'] |
| 559 | inputs = _FixPaths(r.get('inputs', [])) |
| 560 | outputs = _FixPaths(r.get('outputs', [])) |
| 561 | # Skip a rule with no action and no inputs. |
| 562 | if 'action' not in r and not r.get('rule_sources', []): |
| 563 | continue |
| 564 | cmd = _BuildCommandLineForRule(spec, r, has_input_path=True, |
| 565 | do_setup_env=True) |
| 566 | rules_file.AddCustomBuildRule(name=rule_name, |
| 567 | description=r.get('message', rule_name), |
| 568 | extensions=[rule_ext], |
| 569 | additional_dependencies=inputs, |
| 570 | outputs=outputs, |
| 571 | cmd=cmd) |
| 572 | # Write out rules file. |
| 573 | rules_file.WriteIfChanged() |
| 574 | |
| 575 | # Add rules file to project. |
| 576 | p.AddToolFile(rules_filename) |
| 577 | |
| 578 | |
| 579 | def _Cygwinify(path): |
| 580 | path = path.replace('$(OutDir)', '$(OutDirCygwin)') |
| 581 | path = path.replace('$(IntDir)', '$(IntDirCygwin)') |
| 582 | return path |
| 583 | |
| 584 | |
| 585 | def _GenerateExternalRules(rules, output_dir, spec, |
| 586 | sources, options, actions_to_add): |
| 587 | """Generate an external makefile to do a set of rules. |
| 588 | |
| 589 | Arguments: |
| 590 | rules: the list of rules to include |
| 591 | output_dir: path containing project and gyp files |
| 592 | spec: project specification data |
| 593 | sources: set of sources known |
| 594 | options: global generator options |
| 595 | actions_to_add: The list of actions we will add to. |
| 596 | """ |
| 597 | filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix) |
| 598 | mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename)) |
| 599 | # Find cygwin style versions of some paths. |
| 600 | mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n') |
| 601 | mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n') |
| 602 | # Gather stuff needed to emit all: target. |
| 603 | all_inputs = OrderedSet() |
| 604 | all_outputs = OrderedSet() |
| 605 | all_output_dirs = OrderedSet() |
| 606 | first_outputs = [] |
| 607 | for rule in rules: |
| 608 | trigger_files = _FindRuleTriggerFiles(rule, sources) |
| 609 | for tf in trigger_files: |
| 610 | inputs, outputs = _RuleInputsAndOutputs(rule, tf) |
| 611 | all_inputs.update(OrderedSet(inputs)) |
| 612 | all_outputs.update(OrderedSet(outputs)) |
| 613 | # Only use one target from each rule as the dependency for |
| 614 | # 'all' so we don't try to build each rule multiple times. |
| 615 | first_outputs.append(list(outputs)[0]) |
| 616 | # Get the unique output directories for this rule. |
| 617 | output_dirs = [os.path.split(i)[0] for i in outputs] |
| 618 | for od in output_dirs: |
| 619 | all_output_dirs.add(od) |
| 620 | first_outputs_cyg = [_Cygwinify(i) for i in first_outputs] |
| 621 | # Write out all: target, including mkdir for each output directory. |
| 622 | mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg)) |
| 623 | for od in all_output_dirs: |
| 624 | if od: |
| 625 | mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od) |
| 626 | mk_file.write('\n') |
| 627 | # Define how each output is generated. |
| 628 | for rule in rules: |
| 629 | trigger_files = _FindRuleTriggerFiles(rule, sources) |
| 630 | for tf in trigger_files: |
| 631 | # Get all the inputs and outputs for this rule for this trigger file. |
| 632 | inputs, outputs = _RuleInputsAndOutputs(rule, tf) |
| 633 | inputs = [_Cygwinify(i) for i in inputs] |
| 634 | outputs = [_Cygwinify(i) for i in outputs] |
| 635 | # Prepare the command line for this rule. |
| 636 | cmd = [_RuleExpandPath(c, tf) for c in rule['action']] |
| 637 | cmd = ['"%s"' % i for i in cmd] |
| 638 | cmd = ' '.join(cmd) |
| 639 | # Add it to the makefile. |
| 640 | mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs))) |
| 641 | mk_file.write('\t%s\n\n' % cmd) |
| 642 | # Close up the file. |
| 643 | mk_file.close() |
| 644 | |
| 645 | # Add makefile to list of sources. |
| 646 | sources.add(filename) |
| 647 | # Add a build action to call makefile. |
| 648 | cmd = ['make', |
| 649 | 'OutDir=$(OutDir)', |
| 650 | 'IntDir=$(IntDir)', |
| 651 | '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}', |
| 652 | '-f', filename] |
| 653 | cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True, True) |
| 654 | # Insert makefile as 0'th input, so it gets the action attached there, |
| 655 | # as this is easier to understand from in the IDE. |
| 656 | all_inputs = list(all_inputs) |
| 657 | all_inputs.insert(0, filename) |
| 658 | _AddActionStep(actions_to_add, |
| 659 | inputs=_FixPaths(all_inputs), |
| 660 | outputs=_FixPaths(all_outputs), |
| 661 | description='Running external rules for %s' % |
| 662 | spec['target_name'], |
| 663 | command=cmd) |
| 664 | |
| 665 | |
| 666 | def _EscapeEnvironmentVariableExpansion(s): |
| 667 | """Escapes % characters. |
| 668 | |
| 669 | Escapes any % characters so that Windows-style environment variable |
| 670 | expansions will leave them alone. |
| 671 | See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile |
| 672 | to understand why we have to do this. |
| 673 | |
| 674 | Args: |
| 675 | s: The string to be escaped. |
| 676 | |
| 677 | Returns: |
| 678 | The escaped string. |
| 679 | """ |
| 680 | s = s.replace('%', '%%') |
| 681 | return s |
| 682 | |
| 683 | |
| 684 | quote_replacer_regex = re.compile(r'(\\*)"') |
| 685 | |
| 686 | |
| 687 | def _EscapeCommandLineArgumentForMSVS(s): |
| 688 | """Escapes a Windows command-line argument. |
| 689 | |
| 690 | So that the Win32 CommandLineToArgv function will turn the escaped result back |
| 691 | into the original string. |
| 692 | See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx |
| 693 | ("Parsing C++ Command-Line Arguments") to understand why we have to do |
| 694 | this. |
| 695 | |
| 696 | Args: |
| 697 | s: the string to be escaped. |
| 698 | Returns: |
| 699 | the escaped string. |
| 700 | """ |
| 701 | |
| 702 | def _Replace(match): |
| 703 | # For a literal quote, CommandLineToArgv requires an odd number of |
| 704 | # backslashes preceding it, and it produces half as many literal backslashes |
| 705 | # (rounded down). So we need to produce 2n+1 backslashes. |
| 706 | return 2 * match.group(1) + '\\"' |
| 707 | |
| 708 | # Escape all quotes so that they are interpreted literally. |
| 709 | s = quote_replacer_regex.sub(_Replace, s) |
| 710 | # Now add unescaped quotes so that any whitespace is interpreted literally. |
| 711 | s = '"' + s + '"' |
| 712 | return s |
| 713 | |
| 714 | |
| 715 | delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)') |
| 716 | |
| 717 | |
| 718 | def _EscapeVCProjCommandLineArgListItem(s): |
| 719 | """Escapes command line arguments for MSVS. |
| 720 | |
| 721 | The VCProj format stores string lists in a single string using commas and |
| 722 | semi-colons as separators, which must be quoted if they are to be |
| 723 | interpreted literally. However, command-line arguments may already have |
| 724 | quotes, and the VCProj parser is ignorant of the backslash escaping |
| 725 | convention used by CommandLineToArgv, so the command-line quotes and the |
| 726 | VCProj quotes may not be the same quotes. So to store a general |
| 727 | command-line argument in a VCProj list, we need to parse the existing |
| 728 | quoting according to VCProj's convention and quote any delimiters that are |
| 729 | not already quoted by that convention. The quotes that we add will also be |
| 730 | seen by CommandLineToArgv, so if backslashes precede them then we also have |
| 731 | to escape those backslashes according to the CommandLineToArgv |
| 732 | convention. |
| 733 | |
| 734 | Args: |
| 735 | s: the string to be escaped. |
| 736 | Returns: |
| 737 | the escaped string. |
| 738 | """ |
| 739 | |
| 740 | def _Replace(match): |
| 741 | # For a non-literal quote, CommandLineToArgv requires an even number of |
| 742 | # backslashes preceding it, and it produces half as many literal |
| 743 | # backslashes. So we need to produce 2n backslashes. |
| 744 | return 2 * match.group(1) + '"' + match.group(2) + '"' |
| 745 | |
| 746 | segments = s.split('"') |
| 747 | # The unquoted segments are at the even-numbered indices. |
| 748 | for i in range(0, len(segments), 2): |
| 749 | segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i]) |
| 750 | # Concatenate back into a single string |
| 751 | s = '"'.join(segments) |
| 752 | if len(segments) % 2 == 0: |
| 753 | # String ends while still quoted according to VCProj's convention. This |
| 754 | # means the delimiter and the next list item that follow this one in the |
| 755 | # .vcproj file will be misinterpreted as part of this item. There is nothing |
| 756 | # we can do about this. Adding an extra quote would correct the problem in |
| 757 | # the VCProj but cause the same problem on the final command-line. Moving |
| 758 | # the item to the end of the list does works, but that's only possible if |
| 759 | # there's only one such item. Let's just warn the user. |
| 760 | print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' + |
| 761 | 'quotes in ' + s) |
| 762 | return s |
| 763 | |
| 764 | |
| 765 | def _EscapeCppDefineForMSVS(s): |
| 766 | """Escapes a CPP define so that it will reach the compiler unaltered.""" |
| 767 | s = _EscapeEnvironmentVariableExpansion(s) |
| 768 | s = _EscapeCommandLineArgumentForMSVS(s) |
| 769 | s = _EscapeVCProjCommandLineArgListItem(s) |
| 770 | # cl.exe replaces literal # characters with = in preprocesor definitions for |
| 771 | # some reason. Octal-encode to work around that. |
| 772 | s = s.replace('#', '\\%03o' % ord('#')) |
| 773 | return s |
| 774 | |
| 775 | |
| 776 | quote_replacer_regex2 = re.compile(r'(\\+)"') |
| 777 | |
| 778 | |
| 779 | def _EscapeCommandLineArgumentForMSBuild(s): |
| 780 | """Escapes a Windows command-line argument for use by MSBuild.""" |
| 781 | |
| 782 | def _Replace(match): |
| 783 | return (len(match.group(1)) / 2 * 4) * '\\' + '\\"' |
| 784 | |
| 785 | # Escape all quotes so that they are interpreted literally. |
| 786 | s = quote_replacer_regex2.sub(_Replace, s) |
| 787 | return s |
| 788 | |
| 789 | |
| 790 | def _EscapeMSBuildSpecialCharacters(s): |
| 791 | escape_dictionary = { |
| 792 | '%': '%25', |
| 793 | '$': '%24', |
| 794 | '@': '%40', |
| 795 | "'": '%27', |
| 796 | ';': '%3B', |
| 797 | '?': '%3F', |
| 798 | '*': '%2A' |
| 799 | } |
| 800 | result = ''.join([escape_dictionary.get(c, c) for c in s]) |
| 801 | return result |
| 802 | |
| 803 | |
| 804 | def _EscapeCppDefineForMSBuild(s): |
| 805 | """Escapes a CPP define so that it will reach the compiler unaltered.""" |
| 806 | s = _EscapeEnvironmentVariableExpansion(s) |
| 807 | s = _EscapeCommandLineArgumentForMSBuild(s) |
| 808 | s = _EscapeMSBuildSpecialCharacters(s) |
| 809 | # cl.exe replaces literal # characters with = in preprocesor definitions for |
| 810 | # some reason. Octal-encode to work around that. |
| 811 | s = s.replace('#', '\\%03o' % ord('#')) |
| 812 | return s |
| 813 | |
| 814 | |
| 815 | def _GenerateRulesForMSVS(p, output_dir, options, spec, |
| 816 | sources, excluded_sources, |
| 817 | actions_to_add): |
| 818 | """Generate all the rules for a particular project. |
| 819 | |
| 820 | Arguments: |
| 821 | p: the project |
| 822 | output_dir: directory to emit rules to |
| 823 | options: global options passed to the generator |
| 824 | spec: the specification for this project |
| 825 | sources: the set of all known source files in this project |
| 826 | excluded_sources: the set of sources excluded from normal processing |
| 827 | actions_to_add: deferred list of actions to add in |
| 828 | """ |
| 829 | rules = spec.get('rules', []) |
| 830 | rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))] |
| 831 | rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))] |
| 832 | |
| 833 | # Handle rules that use a native rules file. |
| 834 | if rules_native: |
| 835 | _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options) |
| 836 | |
| 837 | # Handle external rules (non-native rules). |
| 838 | if rules_external: |
| 839 | _GenerateExternalRules(rules_external, output_dir, spec, |
| 840 | sources, options, actions_to_add) |
| 841 | _AdjustSourcesForRules(rules, sources, excluded_sources, False) |
| 842 | |
| 843 | |
| 844 | def _AdjustSourcesForRules(rules, sources, excluded_sources, is_msbuild): |
| 845 | # Add outputs generated by each rule (if applicable). |
| 846 | for rule in rules: |
| 847 | # Add in the outputs from this rule. |
| 848 | trigger_files = _FindRuleTriggerFiles(rule, sources) |
| 849 | for trigger_file in trigger_files: |
| 850 | # Remove trigger_file from excluded_sources to let the rule be triggered |
| 851 | # (e.g. rule trigger ax_enums.idl is added to excluded_sources |
| 852 | # because it's also in an action's inputs in the same project) |
| 853 | excluded_sources.discard(_FixPath(trigger_file)) |
| 854 | # Done if not processing outputs as sources. |
| 855 | if int(rule.get('process_outputs_as_sources', False)): |
| 856 | inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file) |
| 857 | inputs = OrderedSet(_FixPaths(inputs)) |
| 858 | outputs = OrderedSet(_FixPaths(outputs)) |
| 859 | inputs.remove(_FixPath(trigger_file)) |
| 860 | sources.update(inputs) |
| 861 | if not is_msbuild: |
| 862 | excluded_sources.update(inputs) |
| 863 | sources.update(outputs) |
| 864 | |
| 865 | |
| 866 | def _FilterActionsFromExcluded(excluded_sources, actions_to_add): |
| 867 | """Take inputs with actions attached out of the list of exclusions. |
| 868 | |
| 869 | Arguments: |
| 870 | excluded_sources: list of source files not to be built. |
| 871 | actions_to_add: dict of actions keyed on source file they're attached to. |
| 872 | Returns: |
| 873 | excluded_sources with files that have actions attached removed. |
| 874 | """ |
| 875 | must_keep = OrderedSet(_FixPaths(actions_to_add.keys())) |
| 876 | return [s for s in excluded_sources if s not in must_keep] |
| 877 | |
| 878 | |
| 879 | def _GetDefaultConfiguration(spec): |
| 880 | return spec['configurations'][spec['default_configuration']] |
| 881 | |
| 882 | |
| 883 | def _GetGuidOfProject(proj_path, spec): |
| 884 | """Get the guid for the project. |
| 885 | |
| 886 | Arguments: |
| 887 | proj_path: Path of the vcproj or vcxproj file to generate. |
| 888 | spec: The target dictionary containing the properties of the target. |
| 889 | Returns: |
| 890 | the guid. |
| 891 | Raises: |
| 892 | ValueError: if the specified GUID is invalid. |
| 893 | """ |
| 894 | # Pluck out the default configuration. |
| 895 | default_config = _GetDefaultConfiguration(spec) |
| 896 | # Decide the guid of the project. |
| 897 | guid = default_config.get('msvs_guid') |
| 898 | if guid: |
| 899 | if VALID_MSVS_GUID_CHARS.match(guid) is None: |
| 900 | raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' % |
| 901 | (guid, VALID_MSVS_GUID_CHARS.pattern)) |
| 902 | guid = '{%s}' % guid |
| 903 | guid = guid or MSVSNew.MakeGuid(proj_path) |
| 904 | return guid |
| 905 | |
| 906 | |
| 907 | def _GetMsbuildToolsetOfProject(proj_path, spec, version): |
| 908 | """Get the platform toolset for the project. |
| 909 | |
| 910 | Arguments: |
| 911 | proj_path: Path of the vcproj or vcxproj file to generate. |
| 912 | spec: The target dictionary containing the properties of the target. |
| 913 | version: The MSVSVersion object. |
| 914 | Returns: |
| 915 | the platform toolset string or None. |
| 916 | """ |
| 917 | # Pluck out the default configuration. |
| 918 | default_config = _GetDefaultConfiguration(spec) |
| 919 | toolset = default_config.get('msbuild_toolset') |
| 920 | if not toolset and version.DefaultToolset(): |
| 921 | toolset = version.DefaultToolset() |
| 922 | return toolset |
| 923 | |
| 924 | |
| 925 | def _GenerateProject(project, options, version, generator_flags): |
| 926 | """Generates a vcproj file. |
| 927 | |
| 928 | Arguments: |
| 929 | project: the MSVSProject object. |
| 930 | options: global generator options. |
| 931 | version: the MSVSVersion object. |
| 932 | generator_flags: dict of generator-specific flags. |
| 933 | Returns: |
| 934 | A list of source files that cannot be found on disk. |
| 935 | """ |
| 936 | default_config = _GetDefaultConfiguration(project.spec) |
| 937 | |
| 938 | # Skip emitting anything if told to with msvs_existing_vcproj option. |
| 939 | if default_config.get('msvs_existing_vcproj'): |
| 940 | return [] |
| 941 | |
| 942 | if version.UsesVcxproj(): |
| 943 | return _GenerateMSBuildProject(project, options, version, generator_flags) |
| 944 | else: |
| 945 | return _GenerateMSVSProject(project, options, version, generator_flags) |
| 946 | |
| 947 | |
| 948 | # TODO: Avoid code duplication with _ValidateSourcesForOSX in make.py. |
| 949 | def _ValidateSourcesForMSVSProject(spec, version): |
| 950 | """Makes sure if duplicate basenames are not specified in the source list. |
| 951 | |
| 952 | Arguments: |
| 953 | spec: The target dictionary containing the properties of the target. |
| 954 | version: The VisualStudioVersion object. |
| 955 | """ |
| 956 | # This validation should not be applied to MSVC2010 and later. |
| 957 | assert not version.UsesVcxproj() |
| 958 | |
| 959 | # TODO: Check if MSVC allows this for loadable_module targets. |
| 960 | if spec.get('type', None) not in ('static_library', 'shared_library'): |
| 961 | return |
| 962 | sources = spec.get('sources', []) |
| 963 | basenames = {} |
| 964 | for source in sources: |
| 965 | name, ext = os.path.splitext(source) |
| 966 | is_compiled_file = ext in [ |
| 967 | '.c', '.cc', '.cpp', '.cxx', '.m', '.mm', '.s', '.S'] |
| 968 | if not is_compiled_file: |
| 969 | continue |
| 970 | basename = os.path.basename(name) # Don't include extension. |
| 971 | basenames.setdefault(basename, []).append(source) |
| 972 | |
| 973 | error = '' |
| 974 | for basename, files in basenames.iteritems(): |
| 975 | if len(files) > 1: |
| 976 | error += ' %s: %s\n' % (basename, ' '.join(files)) |
| 977 | |
| 978 | if error: |
| 979 | print('static library %s has several files with the same basename:\n' % |
| 980 | spec['target_name'] + error + 'MSVC08 cannot handle that.') |
| 981 | raise GypError('Duplicate basenames in sources section, see list above') |
| 982 | |
| 983 | |
| 984 | def _GenerateMSVSProject(project, options, version, generator_flags): |
| 985 | """Generates a .vcproj file. It may create .rules and .user files too. |
| 986 | |
| 987 | Arguments: |
| 988 | project: The project object we will generate the file for. |
| 989 | options: Global options passed to the generator. |
| 990 | version: The VisualStudioVersion object. |
| 991 | generator_flags: dict of generator-specific flags. |
| 992 | """ |
| 993 | spec = project.spec |
| 994 | gyp.common.EnsureDirExists(project.path) |
| 995 | |
| 996 | platforms = _GetUniquePlatforms(spec) |
| 997 | p = MSVSProject.Writer(project.path, version, spec['target_name'], |
| 998 | project.guid, platforms) |
| 999 | |
| 1000 | # Get directory project file is in. |
| 1001 | project_dir = os.path.split(project.path)[0] |
| 1002 | gyp_path = _NormalizedSource(project.build_file) |
| 1003 | relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir) |
| 1004 | |
| 1005 | config_type = _GetMSVSConfigurationType(spec, project.build_file) |
| 1006 | for config_name, config in spec['configurations'].iteritems(): |
| 1007 | _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config) |
| 1008 | |
| 1009 | # MSVC08 and prior version cannot handle duplicate basenames in the same |
| 1010 | # target. |
| 1011 | # TODO: Take excluded sources into consideration if possible. |
| 1012 | _ValidateSourcesForMSVSProject(spec, version) |
| 1013 | |
| 1014 | # Prepare list of sources and excluded sources. |
| 1015 | gyp_file = os.path.split(project.build_file)[1] |
| 1016 | sources, excluded_sources = _PrepareListOfSources(spec, generator_flags, |
| 1017 | gyp_file) |
| 1018 | |
| 1019 | # Add rules. |
| 1020 | actions_to_add = {} |
| 1021 | _GenerateRulesForMSVS(p, project_dir, options, spec, |
| 1022 | sources, excluded_sources, |
| 1023 | actions_to_add) |
| 1024 | list_excluded = generator_flags.get('msvs_list_excluded_files', True) |
| 1025 | sources, excluded_sources, excluded_idl = ( |
| 1026 | _AdjustSourcesAndConvertToFilterHierarchy(spec, options, project_dir, |
| 1027 | sources, excluded_sources, |
| 1028 | list_excluded, version)) |
| 1029 | |
| 1030 | # Add in files. |
| 1031 | missing_sources = _VerifySourcesExist(sources, project_dir) |
| 1032 | p.AddFiles(sources) |
| 1033 | |
| 1034 | _AddToolFilesToMSVS(p, spec) |
| 1035 | _HandlePreCompiledHeaders(p, sources, spec) |
| 1036 | _AddActions(actions_to_add, spec, relative_path_of_gyp_file) |
| 1037 | _AddCopies(actions_to_add, spec) |
| 1038 | _WriteMSVSUserFile(project.path, version, spec) |
| 1039 | |
| 1040 | # NOTE: this stanza must appear after all actions have been decided. |
| 1041 | # Don't excluded sources with actions attached, or they won't run. |
| 1042 | excluded_sources = _FilterActionsFromExcluded( |
| 1043 | excluded_sources, actions_to_add) |
| 1044 | _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl, |
| 1045 | list_excluded) |
| 1046 | _AddAccumulatedActionsToMSVS(p, spec, actions_to_add) |
| 1047 | |
| 1048 | # Write it out. |
| 1049 | p.WriteIfChanged() |
| 1050 | |
| 1051 | return missing_sources |
| 1052 | |
| 1053 | |
| 1054 | def _GetUniquePlatforms(spec): |
| 1055 | """Returns the list of unique platforms for this spec, e.g ['win32', ...]. |
| 1056 | |
| 1057 | Arguments: |
| 1058 | spec: The target dictionary containing the properties of the target. |
| 1059 | Returns: |
| 1060 | The MSVSUserFile object created. |
| 1061 | """ |
| 1062 | # Gather list of unique platforms. |
| 1063 | platforms = OrderedSet() |
| 1064 | for configuration in spec['configurations']: |
| 1065 | platforms.add(_ConfigPlatform(spec['configurations'][configuration])) |
| 1066 | platforms = list(platforms) |
| 1067 | return platforms |
| 1068 | |
| 1069 | |
| 1070 | def _CreateMSVSUserFile(proj_path, version, spec): |
| 1071 | """Generates a .user file for the user running this Gyp program. |
| 1072 | |
| 1073 | Arguments: |
| 1074 | proj_path: The path of the project file being created. The .user file |
| 1075 | shares the same path (with an appropriate suffix). |
| 1076 | version: The VisualStudioVersion object. |
| 1077 | spec: The target dictionary containing the properties of the target. |
| 1078 | Returns: |
| 1079 | The MSVSUserFile object created. |
| 1080 | """ |
| 1081 | (domain, username) = _GetDomainAndUserName() |
| 1082 | vcuser_filename = '.'.join([proj_path, domain, username, 'user']) |
| 1083 | user_file = MSVSUserFile.Writer(vcuser_filename, version, |
| 1084 | spec['target_name']) |
| 1085 | return user_file |
| 1086 | |
| 1087 | |
| 1088 | def _GetMSVSConfigurationType(spec, build_file): |
| 1089 | """Returns the configuration type for this project. |
| 1090 | |
| 1091 | It's a number defined by Microsoft. May raise an exception. |
| 1092 | |
| 1093 | Args: |
| 1094 | spec: The target dictionary containing the properties of the target. |
| 1095 | build_file: The path of the gyp file. |
| 1096 | Returns: |
| 1097 | An integer, the configuration type. |
| 1098 | """ |
| 1099 | try: |
| 1100 | config_type = { |
| 1101 | 'executable': '1', # .exe |
| 1102 | 'shared_library': '2', # .dll |
| 1103 | 'loadable_module': '2', # .dll |
| 1104 | 'static_library': '4', # .lib |
| 1105 | 'none': '10', # Utility type |
| 1106 | }[spec['type']] |
| 1107 | except KeyError: |
| 1108 | if spec.get('type'): |
| 1109 | raise GypError('Target type %s is not a valid target type for ' |
| 1110 | 'target %s in %s.' % |
| 1111 | (spec['type'], spec['target_name'], build_file)) |
| 1112 | else: |
| 1113 | raise GypError('Missing type field for target %s in %s.' % |
| 1114 | (spec['target_name'], build_file)) |
| 1115 | return config_type |
| 1116 | |
| 1117 | |
| 1118 | def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config): |
| 1119 | """Adds a configuration to the MSVS project. |
| 1120 | |
| 1121 | Many settings in a vcproj file are specific to a configuration. This |
| 1122 | function the main part of the vcproj file that's configuration specific. |
| 1123 | |
| 1124 | Arguments: |
| 1125 | p: The target project being generated. |
| 1126 | spec: The target dictionary containing the properties of the target. |
| 1127 | config_type: The configuration type, a number as defined by Microsoft. |
| 1128 | config_name: The name of the configuration. |
| 1129 | config: The dictionary that defines the special processing to be done |
| 1130 | for this configuration. |
| 1131 | """ |
| 1132 | # Get the information for this configuration |
| 1133 | include_dirs, midl_include_dirs, resource_include_dirs = \ |
| 1134 | _GetIncludeDirs(config) |
| 1135 | libraries = _GetLibraries(spec) |
| 1136 | library_dirs = _GetLibraryDirs(config) |
| 1137 | out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec, msbuild=False) |
| 1138 | defines = _GetDefines(config) |
| 1139 | defines = [_EscapeCppDefineForMSVS(d) for d in defines] |
| 1140 | disabled_warnings = _GetDisabledWarnings(config) |
| 1141 | prebuild = config.get('msvs_prebuild') |
| 1142 | postbuild = config.get('msvs_postbuild') |
| 1143 | def_file = _GetModuleDefinition(spec) |
| 1144 | precompiled_header = config.get('msvs_precompiled_header') |
| 1145 | |
| 1146 | # Prepare the list of tools as a dictionary. |
| 1147 | tools = dict() |
| 1148 | # Add in user specified msvs_settings. |
| 1149 | msvs_settings = config.get('msvs_settings', {}) |
| 1150 | MSVSSettings.ValidateMSVSSettings(msvs_settings) |
| 1151 | |
| 1152 | # Prevent default library inheritance from the environment. |
| 1153 | _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', ['$(NOINHERIT)']) |
| 1154 | |
| 1155 | for tool in msvs_settings: |
| 1156 | settings = config['msvs_settings'][tool] |
| 1157 | for setting in settings: |
| 1158 | _ToolAppend(tools, tool, setting, settings[setting]) |
| 1159 | # Add the information to the appropriate tool |
| 1160 | _ToolAppend(tools, 'VCCLCompilerTool', |
| 1161 | 'AdditionalIncludeDirectories', include_dirs) |
| 1162 | _ToolAppend(tools, 'VCMIDLTool', |
| 1163 | 'AdditionalIncludeDirectories', midl_include_dirs) |
| 1164 | _ToolAppend(tools, 'VCResourceCompilerTool', |
| 1165 | 'AdditionalIncludeDirectories', resource_include_dirs) |
| 1166 | # Add in libraries. |
| 1167 | _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries) |
| 1168 | _ToolAppend(tools, 'VCLinkerTool', 'AdditionalLibraryDirectories', |
| 1169 | library_dirs) |
| 1170 | if out_file: |
| 1171 | _ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True) |
| 1172 | # Add defines. |
| 1173 | _ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines) |
| 1174 | _ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions', |
| 1175 | defines) |
| 1176 | # Change program database directory to prevent collisions. |
| 1177 | _ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName', |
| 1178 | '$(IntDir)$(ProjectName)\\vc80.pdb', only_if_unset=True) |
| 1179 | # Add disabled warnings. |
| 1180 | _ToolAppend(tools, 'VCCLCompilerTool', |
| 1181 | 'DisableSpecificWarnings', disabled_warnings) |
| 1182 | # Add Pre-build. |
| 1183 | _ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild) |
| 1184 | # Add Post-build. |
| 1185 | _ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild) |
| 1186 | # Turn on precompiled headers if appropriate. |
| 1187 | if precompiled_header: |
| 1188 | precompiled_header = os.path.split(precompiled_header)[1] |
| 1189 | _ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2') |
| 1190 | _ToolAppend(tools, 'VCCLCompilerTool', |
| 1191 | 'PrecompiledHeaderThrough', precompiled_header) |
| 1192 | _ToolAppend(tools, 'VCCLCompilerTool', |
| 1193 | 'ForcedIncludeFiles', precompiled_header) |
| 1194 | # Loadable modules don't generate import libraries; |
| 1195 | # tell dependent projects to not expect one. |
| 1196 | if spec['type'] == 'loadable_module': |
| 1197 | _ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true') |
| 1198 | # Set the module definition file if any. |
| 1199 | if def_file: |
| 1200 | _ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file) |
| 1201 | |
| 1202 | _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name) |
| 1203 | |
| 1204 | |
| 1205 | def _GetIncludeDirs(config): |
| 1206 | """Returns the list of directories to be used for #include directives. |
| 1207 | |
| 1208 | Arguments: |
| 1209 | config: The dictionary that defines the special processing to be done |
| 1210 | for this configuration. |
| 1211 | Returns: |
| 1212 | The list of directory paths. |
| 1213 | """ |
| 1214 | # TODO(bradnelson): include_dirs should really be flexible enough not to |
| 1215 | # require this sort of thing. |
| 1216 | include_dirs = ( |
| 1217 | config.get('include_dirs', []) + |
| 1218 | config.get('msvs_system_include_dirs', [])) |
| 1219 | midl_include_dirs = ( |
| 1220 | config.get('midl_include_dirs', []) + |
| 1221 | config.get('msvs_system_include_dirs', [])) |
| 1222 | resource_include_dirs = config.get('resource_include_dirs', include_dirs) |
| 1223 | include_dirs = _FixPaths(include_dirs) |
| 1224 | midl_include_dirs = _FixPaths(midl_include_dirs) |
| 1225 | resource_include_dirs = _FixPaths(resource_include_dirs) |
| 1226 | return include_dirs, midl_include_dirs, resource_include_dirs |
| 1227 | |
| 1228 | |
| 1229 | def _GetLibraryDirs(config): |
| 1230 | """Returns the list of directories to be used for library search paths. |
| 1231 | |
| 1232 | Arguments: |
| 1233 | config: The dictionary that defines the special processing to be done |
| 1234 | for this configuration. |
| 1235 | Returns: |
| 1236 | The list of directory paths. |
| 1237 | """ |
| 1238 | |
| 1239 | library_dirs = config.get('library_dirs', []) |
| 1240 | library_dirs = _FixPaths(library_dirs) |
| 1241 | return library_dirs |
| 1242 | |
| 1243 | |
| 1244 | def _GetLibraries(spec): |
| 1245 | """Returns the list of libraries for this configuration. |
| 1246 | |
| 1247 | Arguments: |
| 1248 | spec: The target dictionary containing the properties of the target. |
| 1249 | Returns: |
| 1250 | The list of directory paths. |
| 1251 | """ |
| 1252 | libraries = spec.get('libraries', []) |
| 1253 | # Strip out -l, as it is not used on windows (but is needed so we can pass |
| 1254 | # in libraries that are assumed to be in the default library path). |
| 1255 | # Also remove duplicate entries, leaving only the last duplicate, while |
| 1256 | # preserving order. |
| 1257 | found = OrderedSet() |
| 1258 | unique_libraries_list = [] |
| 1259 | for entry in reversed(libraries): |
| 1260 | library = re.sub(r'^\-l', '', entry) |
| 1261 | if not os.path.splitext(library)[1]: |
| 1262 | library += '.lib' |
| 1263 | if library not in found: |
| 1264 | found.add(library) |
| 1265 | unique_libraries_list.append(library) |
| 1266 | unique_libraries_list.reverse() |
| 1267 | return unique_libraries_list |
| 1268 | |
| 1269 | |
| 1270 | def _GetOutputFilePathAndTool(spec, msbuild): |
| 1271 | """Returns the path and tool to use for this target. |
| 1272 | |
| 1273 | Figures out the path of the file this spec will create and the name of |
| 1274 | the VC tool that will create it. |
| 1275 | |
| 1276 | Arguments: |
| 1277 | spec: The target dictionary containing the properties of the target. |
| 1278 | Returns: |
| 1279 | A triple of (file path, name of the vc tool, name of the msbuild tool) |
| 1280 | """ |
| 1281 | # Select a name for the output file. |
| 1282 | out_file = '' |
| 1283 | vc_tool = '' |
| 1284 | msbuild_tool = '' |
| 1285 | output_file_map = { |
| 1286 | 'executable': ('VCLinkerTool', 'Link', '$(OutDir)', '.exe'), |
| 1287 | 'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'), |
| 1288 | 'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)', '.dll'), |
| 1289 | 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)lib\\', '.lib'), |
| 1290 | } |
| 1291 | output_file_props = output_file_map.get(spec['type']) |
| 1292 | if output_file_props and int(spec.get('msvs_auto_output_file', 1)): |
| 1293 | vc_tool, msbuild_tool, out_dir, suffix = output_file_props |
| 1294 | if spec.get('standalone_static_library', 0): |
| 1295 | out_dir = '$(OutDir)' |
| 1296 | out_dir = spec.get('product_dir', out_dir) |
| 1297 | product_extension = spec.get('product_extension') |
| 1298 | if product_extension: |
| 1299 | suffix = '.' + product_extension |
| 1300 | elif msbuild: |
| 1301 | suffix = '$(TargetExt)' |
| 1302 | prefix = spec.get('product_prefix', '') |
| 1303 | product_name = spec.get('product_name', '$(ProjectName)') |
| 1304 | out_file = ntpath.join(out_dir, prefix + product_name + suffix) |
| 1305 | return out_file, vc_tool, msbuild_tool |
| 1306 | |
| 1307 | |
| 1308 | def _GetOutputTargetExt(spec): |
| 1309 | """Returns the extension for this target, including the dot |
| 1310 | |
| 1311 | If product_extension is specified, set target_extension to this to avoid |
| 1312 | MSB8012, returns None otherwise. Ignores any target_extension settings in |
| 1313 | the input files. |
| 1314 | |
| 1315 | Arguments: |
| 1316 | spec: The target dictionary containing the properties of the target. |
| 1317 | Returns: |
| 1318 | A string with the extension, or None |
| 1319 | """ |
| 1320 | target_extension = spec.get('product_extension') |
| 1321 | if target_extension: |
| 1322 | return '.' + target_extension |
| 1323 | return None |
| 1324 | |
| 1325 | |
| 1326 | def _GetDefines(config): |
| 1327 | """Returns the list of preprocessor definitions for this configuation. |
| 1328 | |
| 1329 | Arguments: |
| 1330 | config: The dictionary that defines the special processing to be done |
| 1331 | for this configuration. |
| 1332 | Returns: |
| 1333 | The list of preprocessor definitions. |
| 1334 | """ |
| 1335 | defines = [] |
| 1336 | for d in config.get('defines', []): |
| 1337 | if type(d) == list: |
| 1338 | fd = '='.join([str(dpart) for dpart in d]) |
| 1339 | else: |
| 1340 | fd = str(d) |
| 1341 | defines.append(fd) |
| 1342 | return defines |
| 1343 | |
| 1344 | |
| 1345 | def _GetDisabledWarnings(config): |
| 1346 | return [str(i) for i in config.get('msvs_disabled_warnings', [])] |
| 1347 | |
| 1348 | |
| 1349 | def _GetModuleDefinition(spec): |
| 1350 | def_file = '' |
| 1351 | if spec['type'] in ['shared_library', 'loadable_module', 'executable']: |
| 1352 | def_files = [s for s in spec.get('sources', []) if s.endswith('.def')] |
| 1353 | if len(def_files) == 1: |
| 1354 | def_file = _FixPath(def_files[0]) |
| 1355 | elif def_files: |
| 1356 | raise ValueError( |
| 1357 | 'Multiple module definition files in one target, target %s lists ' |
| 1358 | 'multiple .def files: %s' % ( |
| 1359 | spec['target_name'], ' '.join(def_files))) |
| 1360 | return def_file |
| 1361 | |
| 1362 | |
| 1363 | def _ConvertToolsToExpectedForm(tools): |
| 1364 | """Convert tools to a form expected by Visual Studio. |
| 1365 | |
| 1366 | Arguments: |
| 1367 | tools: A dictionary of settings; the tool name is the key. |
| 1368 | Returns: |
| 1369 | A list of Tool objects. |
| 1370 | """ |
| 1371 | tool_list = [] |
| 1372 | for tool, settings in tools.iteritems(): |
| 1373 | # Collapse settings with lists. |
| 1374 | settings_fixed = {} |
| 1375 | for setting, value in settings.iteritems(): |
| 1376 | if type(value) == list: |
| 1377 | if ((tool == 'VCLinkerTool' and |
| 1378 | setting == 'AdditionalDependencies') or |
| 1379 | setting == 'AdditionalOptions'): |
| 1380 | settings_fixed[setting] = ' '.join(value) |
| 1381 | else: |
| 1382 | settings_fixed[setting] = ';'.join(value) |
| 1383 | else: |
| 1384 | settings_fixed[setting] = value |
| 1385 | # Add in this tool. |
| 1386 | tool_list.append(MSVSProject.Tool(tool, settings_fixed)) |
| 1387 | return tool_list |
| 1388 | |
| 1389 | |
| 1390 | def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name): |
| 1391 | """Add to the project file the configuration specified by config. |
| 1392 | |
| 1393 | Arguments: |
| 1394 | p: The target project being generated. |
| 1395 | spec: the target project dict. |
| 1396 | tools: A dictionary of settings; the tool name is the key. |
| 1397 | config: The dictionary that defines the special processing to be done |
| 1398 | for this configuration. |
| 1399 | config_type: The configuration type, a number as defined by Microsoft. |
| 1400 | config_name: The name of the configuration. |
| 1401 | """ |
| 1402 | attributes = _GetMSVSAttributes(spec, config, config_type) |
| 1403 | # Add in this configuration. |
| 1404 | tool_list = _ConvertToolsToExpectedForm(tools) |
| 1405 | p.AddConfig(_ConfigFullName(config_name, config), |
| 1406 | attrs=attributes, tools=tool_list) |
| 1407 | |
| 1408 | |
| 1409 | def _GetMSVSAttributes(spec, config, config_type): |
| 1410 | # Prepare configuration attributes. |
| 1411 | prepared_attrs = {} |
| 1412 | source_attrs = config.get('msvs_configuration_attributes', {}) |
| 1413 | for a in source_attrs: |
| 1414 | prepared_attrs[a] = source_attrs[a] |
| 1415 | # Add props files. |
| 1416 | vsprops_dirs = config.get('msvs_props', []) |
| 1417 | vsprops_dirs = _FixPaths(vsprops_dirs) |
| 1418 | if vsprops_dirs: |
| 1419 | prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs) |
| 1420 | # Set configuration type. |
| 1421 | prepared_attrs['ConfigurationType'] = config_type |
| 1422 | output_dir = prepared_attrs.get('OutputDirectory', |
| 1423 | '$(SolutionDir)$(ConfigurationName)') |
| 1424 | prepared_attrs['OutputDirectory'] = _FixPath(output_dir) + '\\' |
| 1425 | if 'IntermediateDirectory' not in prepared_attrs: |
| 1426 | intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)' |
| 1427 | prepared_attrs['IntermediateDirectory'] = _FixPath(intermediate) + '\\' |
| 1428 | else: |
| 1429 | intermediate = _FixPath(prepared_attrs['IntermediateDirectory']) + '\\' |
| 1430 | intermediate = MSVSSettings.FixVCMacroSlashes(intermediate) |
| 1431 | prepared_attrs['IntermediateDirectory'] = intermediate |
| 1432 | return prepared_attrs |
| 1433 | |
| 1434 | |
| 1435 | def _AddNormalizedSources(sources_set, sources_array): |
| 1436 | sources_set.update(_NormalizedSource(s) for s in sources_array) |
| 1437 | |
| 1438 | |
| 1439 | def _PrepareListOfSources(spec, generator_flags, gyp_file): |
| 1440 | """Prepare list of sources and excluded sources. |
| 1441 | |
| 1442 | Besides the sources specified directly in the spec, adds the gyp file so |
| 1443 | that a change to it will cause a re-compile. Also adds appropriate sources |
| 1444 | for actions and copies. Assumes later stage will un-exclude files which |
| 1445 | have custom build steps attached. |
| 1446 | |
| 1447 | Arguments: |
| 1448 | spec: The target dictionary containing the properties of the target. |
| 1449 | gyp_file: The name of the gyp file. |
| 1450 | Returns: |
| 1451 | A pair of (list of sources, list of excluded sources). |
| 1452 | The sources will be relative to the gyp file. |
| 1453 | """ |
| 1454 | sources = OrderedSet() |
| 1455 | _AddNormalizedSources(sources, spec.get('sources', [])) |
| 1456 | excluded_sources = OrderedSet() |
| 1457 | # Add in the gyp file. |
| 1458 | if not generator_flags.get('standalone'): |
| 1459 | sources.add(gyp_file) |
| 1460 | |
| 1461 | # Add in 'action' inputs and outputs. |
| 1462 | for a in spec.get('actions', []): |
| 1463 | inputs = a['inputs'] |
| 1464 | inputs = [_NormalizedSource(i) for i in inputs] |
| 1465 | # Add all inputs to sources and excluded sources. |
| 1466 | inputs = OrderedSet(inputs) |
| 1467 | sources.update(inputs) |
| 1468 | if not spec.get('msvs_external_builder'): |
| 1469 | excluded_sources.update(inputs) |
| 1470 | if int(a.get('process_outputs_as_sources', False)): |
| 1471 | _AddNormalizedSources(sources, a.get('outputs', [])) |
| 1472 | # Add in 'copies' inputs and outputs. |
| 1473 | for cpy in spec.get('copies', []): |
| 1474 | _AddNormalizedSources(sources, cpy.get('files', [])) |
| 1475 | return (sources, excluded_sources) |
| 1476 | |
| 1477 | |
| 1478 | def _AdjustSourcesAndConvertToFilterHierarchy( |
| 1479 | spec, options, gyp_dir, sources, excluded_sources, list_excluded, version): |
| 1480 | """Adjusts the list of sources and excluded sources. |
| 1481 | |
| 1482 | Also converts the sets to lists. |
| 1483 | |
| 1484 | Arguments: |
| 1485 | spec: The target dictionary containing the properties of the target. |
| 1486 | options: Global generator options. |
| 1487 | gyp_dir: The path to the gyp file being processed. |
| 1488 | sources: A set of sources to be included for this project. |
| 1489 | excluded_sources: A set of sources to be excluded for this project. |
| 1490 | version: A MSVSVersion object. |
| 1491 | Returns: |
| 1492 | A trio of (list of sources, list of excluded sources, |
| 1493 | path of excluded IDL file) |
| 1494 | """ |
| 1495 | # Exclude excluded sources coming into the generator. |
| 1496 | excluded_sources.update(OrderedSet(spec.get('sources_excluded', []))) |
| 1497 | # Add excluded sources into sources for good measure. |
| 1498 | sources.update(excluded_sources) |
| 1499 | # Convert to proper windows form. |
| 1500 | # NOTE: sources goes from being a set to a list here. |
| 1501 | # NOTE: excluded_sources goes from being a set to a list here. |
| 1502 | sources = _FixPaths(sources) |
| 1503 | # Convert to proper windows form. |
| 1504 | excluded_sources = _FixPaths(excluded_sources) |
| 1505 | |
| 1506 | excluded_idl = _IdlFilesHandledNonNatively(spec, sources) |
| 1507 | |
| 1508 | precompiled_related = _GetPrecompileRelatedFiles(spec) |
| 1509 | # Find the excluded ones, minus the precompiled header related ones. |
| 1510 | fully_excluded = [i for i in excluded_sources if i not in precompiled_related] |
| 1511 | |
| 1512 | # Convert to folders and the right slashes. |
| 1513 | sources = [i.split('\\') for i in sources] |
| 1514 | sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded, |
| 1515 | list_excluded=list_excluded, |
| 1516 | msvs_version=version) |
| 1517 | |
| 1518 | # Prune filters with a single child to flatten ugly directory structures |
| 1519 | # such as ../../src/modules/module1 etc. |
| 1520 | if version.UsesVcxproj(): |
| 1521 | while all([isinstance(s, MSVSProject.Filter) for s in sources]) \ |
| 1522 | and len(set([s.name for s in sources])) == 1: |
| 1523 | assert all([len(s.contents) == 1 for s in sources]) |
| 1524 | sources = [s.contents[0] for s in sources] |
| 1525 | else: |
| 1526 | while len(sources) == 1 and isinstance(sources[0], MSVSProject.Filter): |
| 1527 | sources = sources[0].contents |
| 1528 | |
| 1529 | return sources, excluded_sources, excluded_idl |
| 1530 | |
| 1531 | |
| 1532 | def _IdlFilesHandledNonNatively(spec, sources): |
| 1533 | # If any non-native rules use 'idl' as an extension exclude idl files. |
| 1534 | # Gather a list here to use later. |
| 1535 | using_idl = False |
| 1536 | for rule in spec.get('rules', []): |
| 1537 | if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)): |
| 1538 | using_idl = True |
| 1539 | break |
| 1540 | if using_idl: |
| 1541 | excluded_idl = [i for i in sources if i.endswith('.idl')] |
| 1542 | else: |
| 1543 | excluded_idl = [] |
| 1544 | return excluded_idl |
| 1545 | |
| 1546 | |
| 1547 | def _GetPrecompileRelatedFiles(spec): |
| 1548 | # Gather a list of precompiled header related sources. |
| 1549 | precompiled_related = [] |
| 1550 | for _, config in spec['configurations'].iteritems(): |
| 1551 | for k in precomp_keys: |
| 1552 | f = config.get(k) |
| 1553 | if f: |
| 1554 | precompiled_related.append(_FixPath(f)) |
| 1555 | return precompiled_related |
| 1556 | |
| 1557 | |
| 1558 | def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl, |
| 1559 | list_excluded): |
| 1560 | exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl) |
| 1561 | for file_name, excluded_configs in exclusions.iteritems(): |
| 1562 | if (not list_excluded and |
| 1563 | len(excluded_configs) == len(spec['configurations'])): |
| 1564 | # If we're not listing excluded files, then they won't appear in the |
| 1565 | # project, so don't try to configure them to be excluded. |
| 1566 | pass |
| 1567 | else: |
| 1568 | for config_name, config in excluded_configs: |
| 1569 | p.AddFileConfig(file_name, _ConfigFullName(config_name, config), |
| 1570 | {'ExcludedFromBuild': 'true'}) |
| 1571 | |
| 1572 | |
| 1573 | def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl): |
| 1574 | exclusions = {} |
| 1575 | # Exclude excluded sources from being built. |
| 1576 | for f in excluded_sources: |
| 1577 | excluded_configs = [] |
| 1578 | for config_name, config in spec['configurations'].iteritems(): |
| 1579 | precomped = [_FixPath(config.get(i, '')) for i in precomp_keys] |
| 1580 | # Don't do this for ones that are precompiled header related. |
| 1581 | if f not in precomped: |
| 1582 | excluded_configs.append((config_name, config)) |
| 1583 | exclusions[f] = excluded_configs |
| 1584 | # If any non-native rules use 'idl' as an extension exclude idl files. |
| 1585 | # Exclude them now. |
| 1586 | for f in excluded_idl: |
| 1587 | excluded_configs = [] |
| 1588 | for config_name, config in spec['configurations'].iteritems(): |
| 1589 | excluded_configs.append((config_name, config)) |
| 1590 | exclusions[f] = excluded_configs |
| 1591 | return exclusions |
| 1592 | |
| 1593 | |
| 1594 | def _AddToolFilesToMSVS(p, spec): |
| 1595 | # Add in tool files (rules). |
| 1596 | tool_files = OrderedSet() |
| 1597 | for _, config in spec['configurations'].iteritems(): |
| 1598 | for f in config.get('msvs_tool_files', []): |
| 1599 | tool_files.add(f) |
| 1600 | for f in tool_files: |
| 1601 | p.AddToolFile(f) |
| 1602 | |
| 1603 | |
| 1604 | def _HandlePreCompiledHeaders(p, sources, spec): |
| 1605 | # Pre-compiled header source stubs need a different compiler flag |
| 1606 | # (generate precompiled header) and any source file not of the same |
| 1607 | # kind (i.e. C vs. C++) as the precompiled header source stub needs |
| 1608 | # to have use of precompiled headers disabled. |
| 1609 | extensions_excluded_from_precompile = [] |
| 1610 | for config_name, config in spec['configurations'].iteritems(): |
| 1611 | source = config.get('msvs_precompiled_source') |
| 1612 | if source: |
| 1613 | source = _FixPath(source) |
| 1614 | # UsePrecompiledHeader=1 for if using precompiled headers. |
| 1615 | tool = MSVSProject.Tool('VCCLCompilerTool', |
| 1616 | {'UsePrecompiledHeader': '1'}) |
| 1617 | p.AddFileConfig(source, _ConfigFullName(config_name, config), |
| 1618 | {}, tools=[tool]) |
| 1619 | basename, extension = os.path.splitext(source) |
| 1620 | if extension == '.c': |
| 1621 | extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx'] |
| 1622 | else: |
| 1623 | extensions_excluded_from_precompile = ['.c'] |
| 1624 | def DisableForSourceTree(source_tree): |
| 1625 | for source in source_tree: |
| 1626 | if isinstance(source, MSVSProject.Filter): |
| 1627 | DisableForSourceTree(source.contents) |
| 1628 | else: |
| 1629 | basename, extension = os.path.splitext(source) |
| 1630 | if extension in extensions_excluded_from_precompile: |
| 1631 | for config_name, config in spec['configurations'].iteritems(): |
| 1632 | tool = MSVSProject.Tool('VCCLCompilerTool', |
| 1633 | {'UsePrecompiledHeader': '0', |
| 1634 | 'ForcedIncludeFiles': '$(NOINHERIT)'}) |
| 1635 | p.AddFileConfig(_FixPath(source), |
| 1636 | _ConfigFullName(config_name, config), |
| 1637 | {}, tools=[tool]) |
| 1638 | # Do nothing if there was no precompiled source. |
| 1639 | if extensions_excluded_from_precompile: |
| 1640 | DisableForSourceTree(sources) |
| 1641 | |
| 1642 | |
| 1643 | def _AddActions(actions_to_add, spec, relative_path_of_gyp_file): |
| 1644 | # Add actions. |
| 1645 | actions = spec.get('actions', []) |
| 1646 | # Don't setup_env every time. When all the actions are run together in one |
| 1647 | # batch file in VS, the PATH will grow too long. |
| 1648 | # Membership in this set means that the cygwin environment has been set up, |
| 1649 | # and does not need to be set up again. |
| 1650 | have_setup_env = set() |
| 1651 | for a in actions: |
| 1652 | # Attach actions to the gyp file if nothing else is there. |
| 1653 | inputs = a.get('inputs') or [relative_path_of_gyp_file] |
| 1654 | attached_to = inputs[0] |
| 1655 | need_setup_env = attached_to not in have_setup_env |
| 1656 | cmd = _BuildCommandLineForRule(spec, a, has_input_path=False, |
| 1657 | do_setup_env=need_setup_env) |
| 1658 | have_setup_env.add(attached_to) |
| 1659 | # Add the action. |
| 1660 | _AddActionStep(actions_to_add, |
| 1661 | inputs=inputs, |
| 1662 | outputs=a.get('outputs', []), |
| 1663 | description=a.get('message', a['action_name']), |
| 1664 | command=cmd) |
| 1665 | |
| 1666 | |
| 1667 | def _WriteMSVSUserFile(project_path, version, spec): |
| 1668 | # Add run_as and test targets. |
| 1669 | if 'run_as' in spec: |
| 1670 | run_as = spec['run_as'] |
| 1671 | action = run_as.get('action', []) |
| 1672 | environment = run_as.get('environment', []) |
| 1673 | working_directory = run_as.get('working_directory', '.') |
| 1674 | elif int(spec.get('test', 0)): |
| 1675 | action = ['$(TargetPath)', '--gtest_print_time'] |
| 1676 | environment = [] |
| 1677 | working_directory = '.' |
| 1678 | else: |
| 1679 | return # Nothing to add |
| 1680 | # Write out the user file. |
| 1681 | user_file = _CreateMSVSUserFile(project_path, version, spec) |
| 1682 | for config_name, c_data in spec['configurations'].iteritems(): |
| 1683 | user_file.AddDebugSettings(_ConfigFullName(config_name, c_data), |
| 1684 | action, environment, working_directory) |
| 1685 | user_file.WriteIfChanged() |
| 1686 | |
| 1687 | |
| 1688 | def _AddCopies(actions_to_add, spec): |
| 1689 | copies = _GetCopies(spec) |
| 1690 | for inputs, outputs, cmd, description in copies: |
| 1691 | _AddActionStep(actions_to_add, inputs=inputs, outputs=outputs, |
| 1692 | description=description, command=cmd) |
| 1693 | |
| 1694 | |
| 1695 | def _GetCopies(spec): |
| 1696 | copies = [] |
| 1697 | # Add copies. |
| 1698 | for cpy in spec.get('copies', []): |
| 1699 | for src in cpy.get('files', []): |
| 1700 | dst = os.path.join(cpy['destination'], os.path.basename(src)) |
| 1701 | # _AddCustomBuildToolForMSVS() will call _FixPath() on the inputs and |
| 1702 | # outputs, so do the same for our generated command line. |
| 1703 | if src.endswith('/'): |
| 1704 | src_bare = src[:-1] |
| 1705 | base_dir = posixpath.split(src_bare)[0] |
| 1706 | outer_dir = posixpath.split(src_bare)[1] |
| 1707 | cmd = 'cd "%s" && xcopy /e /f /y "%s" "%s\\%s\\"' % ( |
| 1708 | _FixPath(base_dir), outer_dir, _FixPath(dst), outer_dir) |
| 1709 | copies.append(([src], ['dummy_copies', dst], cmd, |
| 1710 | 'Copying %s to %s' % (src, dst))) |
| 1711 | else: |
| 1712 | cmd = 'mkdir "%s" 2>nul & set ERRORLEVEL=0 & copy /Y "%s" "%s"' % ( |
| 1713 | _FixPath(cpy['destination']), _FixPath(src), _FixPath(dst)) |
| 1714 | copies.append(([src], [dst], cmd, 'Copying %s to %s' % (src, dst))) |
| 1715 | return copies |
| 1716 | |
| 1717 | |
| 1718 | def _GetPathDict(root, path): |
| 1719 | # |path| will eventually be empty (in the recursive calls) if it was initially |
| 1720 | # relative; otherwise it will eventually end up as '\', 'D:\', etc. |
| 1721 | if not path or path.endswith(os.sep): |
| 1722 | return root |
| 1723 | parent, folder = os.path.split(path) |
| 1724 | parent_dict = _GetPathDict(root, parent) |
| 1725 | if folder not in parent_dict: |
| 1726 | parent_dict[folder] = dict() |
| 1727 | return parent_dict[folder] |
| 1728 | |
| 1729 | |
| 1730 | def _DictsToFolders(base_path, bucket, flat): |
| 1731 | # Convert to folders recursively. |
| 1732 | children = [] |
| 1733 | for folder, contents in bucket.iteritems(): |
| 1734 | if type(contents) == dict: |
| 1735 | folder_children = _DictsToFolders(os.path.join(base_path, folder), |
| 1736 | contents, flat) |
| 1737 | if flat: |
| 1738 | children += folder_children |
| 1739 | else: |
| 1740 | folder_children = MSVSNew.MSVSFolder(os.path.join(base_path, folder), |
| 1741 | name='(' + folder + ')', |
| 1742 | entries=folder_children) |
| 1743 | children.append(folder_children) |
| 1744 | else: |
| 1745 | children.append(contents) |
| 1746 | return children |
| 1747 | |
| 1748 | |
| 1749 | def _CollapseSingles(parent, node): |
| 1750 | # Recursively explorer the tree of dicts looking for projects which are |
| 1751 | # the sole item in a folder which has the same name as the project. Bring |
| 1752 | # such projects up one level. |
| 1753 | if (type(node) == dict and |
| 1754 | len(node) == 1 and |
| 1755 | node.keys()[0] == parent + '.vcproj'): |
| 1756 | return node[node.keys()[0]] |
| 1757 | if type(node) != dict: |
| 1758 | return node |
| 1759 | for child in node: |
| 1760 | node[child] = _CollapseSingles(child, node[child]) |
| 1761 | return node |
| 1762 | |
| 1763 | |
| 1764 | def _GatherSolutionFolders(sln_projects, project_objects, flat): |
| 1765 | root = {} |
| 1766 | # Convert into a tree of dicts on path. |
| 1767 | for p in sln_projects: |
| 1768 | gyp_file, target = gyp.common.ParseQualifiedTarget(p)[0:2] |
| 1769 | gyp_dir = os.path.dirname(gyp_file) |
| 1770 | path_dict = _GetPathDict(root, gyp_dir) |
| 1771 | path_dict[target + '.vcproj'] = project_objects[p] |
| 1772 | # Walk down from the top until we hit a folder that has more than one entry. |
| 1773 | # In practice, this strips the top-level "src/" dir from the hierarchy in |
| 1774 | # the solution. |
| 1775 | while len(root) == 1 and type(root[root.keys()[0]]) == dict: |
| 1776 | root = root[root.keys()[0]] |
| 1777 | # Collapse singles. |
| 1778 | root = _CollapseSingles('', root) |
| 1779 | # Merge buckets until everything is a root entry. |
| 1780 | return _DictsToFolders('', root, flat) |
| 1781 | |
| 1782 | |
| 1783 | def _GetPathOfProject(qualified_target, spec, options, msvs_version): |
| 1784 | default_config = _GetDefaultConfiguration(spec) |
| 1785 | proj_filename = default_config.get('msvs_existing_vcproj') |
| 1786 | if not proj_filename: |
| 1787 | proj_filename = (spec['target_name'] + options.suffix + |
| 1788 | msvs_version.ProjectExtension()) |
| 1789 | |
| 1790 | build_file = gyp.common.BuildFile(qualified_target) |
| 1791 | proj_path = os.path.join(os.path.dirname(build_file), proj_filename) |
| 1792 | fix_prefix = None |
| 1793 | if options.generator_output: |
| 1794 | project_dir_path = os.path.dirname(os.path.abspath(proj_path)) |
| 1795 | proj_path = os.path.join(options.generator_output, proj_path) |
| 1796 | fix_prefix = gyp.common.RelativePath(project_dir_path, |
| 1797 | os.path.dirname(proj_path)) |
| 1798 | return proj_path, fix_prefix |
| 1799 | |
| 1800 | |
| 1801 | def _GetPlatformOverridesOfProject(spec): |
| 1802 | # Prepare a dict indicating which project configurations are used for which |
| 1803 | # solution configurations for this target. |
| 1804 | config_platform_overrides = {} |
| 1805 | for config_name, c in spec['configurations'].iteritems(): |
| 1806 | config_fullname = _ConfigFullName(config_name, c) |
| 1807 | platform = c.get('msvs_target_platform', _ConfigPlatform(c)) |
| 1808 | fixed_config_fullname = '%s|%s' % ( |
| 1809 | _ConfigBaseName(config_name, _ConfigPlatform(c)), platform) |
| 1810 | config_platform_overrides[config_fullname] = fixed_config_fullname |
| 1811 | return config_platform_overrides |
| 1812 | |
| 1813 | |
| 1814 | def _CreateProjectObjects(target_list, target_dicts, options, msvs_version): |
| 1815 | """Create a MSVSProject object for the targets found in target list. |
| 1816 | |
| 1817 | Arguments: |
| 1818 | target_list: the list of targets to generate project objects for. |
| 1819 | target_dicts: the dictionary of specifications. |
| 1820 | options: global generator options. |
| 1821 | msvs_version: the MSVSVersion object. |
| 1822 | Returns: |
| 1823 | A set of created projects, keyed by target. |
| 1824 | """ |
| 1825 | global fixpath_prefix |
| 1826 | # Generate each project. |
| 1827 | projects = {} |
| 1828 | for qualified_target in target_list: |
| 1829 | spec = target_dicts[qualified_target] |
| 1830 | if spec['toolset'] != 'target': |
| 1831 | raise GypError( |
| 1832 | 'Multiple toolsets not supported in msvs build (target %s)' % |
| 1833 | qualified_target) |
| 1834 | proj_path, fixpath_prefix = _GetPathOfProject(qualified_target, spec, |
| 1835 | options, msvs_version) |
| 1836 | guid = _GetGuidOfProject(proj_path, spec) |
| 1837 | overrides = _GetPlatformOverridesOfProject(spec) |
| 1838 | build_file = gyp.common.BuildFile(qualified_target) |
| 1839 | # Create object for this project. |
| 1840 | obj = MSVSNew.MSVSProject( |
| 1841 | proj_path, |
| 1842 | name=spec['target_name'], |
| 1843 | guid=guid, |
| 1844 | spec=spec, |
| 1845 | build_file=build_file, |
| 1846 | config_platform_overrides=overrides, |
| 1847 | fixpath_prefix=fixpath_prefix) |
| 1848 | # Set project toolset if any (MS build only) |
| 1849 | if msvs_version.UsesVcxproj(): |
| 1850 | obj.set_msbuild_toolset( |
| 1851 | _GetMsbuildToolsetOfProject(proj_path, spec, msvs_version)) |
| 1852 | projects[qualified_target] = obj |
| 1853 | # Set all the dependencies, but not if we are using an external builder like |
| 1854 | # ninja |
| 1855 | for project in projects.values(): |
| 1856 | if not project.spec.get('msvs_external_builder'): |
| 1857 | deps = project.spec.get('dependencies', []) |
| 1858 | deps = [projects[d] for d in deps] |
| 1859 | project.set_dependencies(deps) |
| 1860 | return projects |
| 1861 | |
| 1862 | |
| 1863 | def _InitNinjaFlavor(params, target_list, target_dicts): |
| 1864 | """Initialize targets for the ninja flavor. |
| 1865 | |
| 1866 | This sets up the necessary variables in the targets to generate msvs projects |
| 1867 | that use ninja as an external builder. The variables in the spec are only set |
| 1868 | if they have not been set. This allows individual specs to override the |
| 1869 | default values initialized here. |
| 1870 | Arguments: |
| 1871 | params: Params provided to the generator. |
| 1872 | target_list: List of target pairs: 'base/base.gyp:base'. |
| 1873 | target_dicts: Dict of target properties keyed on target pair. |
| 1874 | """ |
| 1875 | for qualified_target in target_list: |
| 1876 | spec = target_dicts[qualified_target] |
| 1877 | if spec.get('msvs_external_builder'): |
| 1878 | # The spec explicitly defined an external builder, so don't change it. |
| 1879 | continue |
| 1880 | |
| 1881 | path_to_ninja = spec.get('msvs_path_to_ninja', 'ninja.exe') |
| 1882 | |
| 1883 | spec['msvs_external_builder'] = 'ninja' |
| 1884 | if not spec.get('msvs_external_builder_out_dir'): |
| 1885 | gyp_file, _, _ = gyp.common.ParseQualifiedTarget(qualified_target) |
| 1886 | gyp_dir = os.path.dirname(gyp_file) |
| 1887 | configuration = '$(Configuration)' |
| 1888 | if params.get('target_arch') == 'x64': |
| 1889 | configuration += '_x64' |
| 1890 | spec['msvs_external_builder_out_dir'] = os.path.join( |
| 1891 | gyp.common.RelativePath(params['options'].toplevel_dir, gyp_dir), |
| 1892 | ninja_generator.ComputeOutputDir(params), |
| 1893 | configuration) |
| 1894 | if not spec.get('msvs_external_builder_build_cmd'): |
| 1895 | spec['msvs_external_builder_build_cmd'] = [ |
| 1896 | path_to_ninja, |
| 1897 | '-C', |
| 1898 | '$(OutDir)', |
| 1899 | '$(ProjectName)', |
| 1900 | ] |
| 1901 | if not spec.get('msvs_external_builder_clean_cmd'): |
| 1902 | spec['msvs_external_builder_clean_cmd'] = [ |
| 1903 | path_to_ninja, |
| 1904 | '-C', |
| 1905 | '$(OutDir)', |
| 1906 | '-tclean', |
| 1907 | '$(ProjectName)', |
| 1908 | ] |
| 1909 | |
| 1910 | |
| 1911 | def CalculateVariables(default_variables, params): |
| 1912 | """Generated variables that require params to be known.""" |
| 1913 | |
| 1914 | generator_flags = params.get('generator_flags', {}) |
| 1915 | |
| 1916 | # Select project file format version (if unset, default to auto detecting). |
| 1917 | msvs_version = MSVSVersion.SelectVisualStudioVersion( |
| 1918 | generator_flags.get('msvs_version', 'auto')) |
| 1919 | # Stash msvs_version for later (so we don't have to probe the system twice). |
| 1920 | params['msvs_version'] = msvs_version |
| 1921 | |
| 1922 | # Set a variable so conditions can be based on msvs_version. |
| 1923 | default_variables['MSVS_VERSION'] = msvs_version.ShortName() |
| 1924 | |
| 1925 | # To determine processor word size on Windows, in addition to checking |
| 1926 | # PROCESSOR_ARCHITECTURE (which reflects the word size of the current |
| 1927 | # process), it is also necessary to check PROCESSOR_ARCITEW6432 (which |
| 1928 | # contains the actual word size of the system when running thru WOW64). |
| 1929 | if (os.environ.get('PROCESSOR_ARCHITECTURE', '').find('64') >= 0 or |
| 1930 | os.environ.get('PROCESSOR_ARCHITEW6432', '').find('64') >= 0): |
| 1931 | default_variables['MSVS_OS_BITS'] = 64 |
| 1932 | else: |
| 1933 | default_variables['MSVS_OS_BITS'] = 32 |
| 1934 | |
| 1935 | if gyp.common.GetFlavor(params) == 'ninja': |
| 1936 | default_variables['SHARED_INTERMEDIATE_DIR'] = '$(OutDir)gen' |
| 1937 | |
| 1938 | |
| 1939 | def PerformBuild(data, configurations, params): |
| 1940 | options = params['options'] |
| 1941 | msvs_version = params['msvs_version'] |
| 1942 | devenv = os.path.join(msvs_version.path, 'Common7', 'IDE', 'devenv.com') |
| 1943 | |
| 1944 | for build_file, build_file_dict in data.iteritems(): |
| 1945 | (build_file_root, build_file_ext) = os.path.splitext(build_file) |
| 1946 | if build_file_ext != '.gyp': |
| 1947 | continue |
| 1948 | sln_path = build_file_root + options.suffix + '.sln' |
| 1949 | if options.generator_output: |
| 1950 | sln_path = os.path.join(options.generator_output, sln_path) |
| 1951 | |
| 1952 | for config in configurations: |
| 1953 | arguments = [devenv, sln_path, '/Build', config] |
| 1954 | print 'Building [%s]: %s' % (config, arguments) |
| 1955 | rtn = subprocess.check_call(arguments) |
| 1956 | |
| 1957 | |
| 1958 | def CalculateGeneratorInputInfo(params): |
| 1959 | if params.get('flavor') == 'ninja': |
| 1960 | toplevel = params['options'].toplevel_dir |
| 1961 | qualified_out_dir = os.path.normpath(os.path.join( |
| 1962 | toplevel, ninja_generator.ComputeOutputDir(params), |
| 1963 | 'gypfiles-msvs-ninja')) |
| 1964 | |
| 1965 | global generator_filelist_paths |
| 1966 | generator_filelist_paths = { |
| 1967 | 'toplevel': toplevel, |
| 1968 | 'qualified_out_dir': qualified_out_dir, |
| 1969 | } |
| 1970 | |
| 1971 | def GenerateOutput(target_list, target_dicts, data, params): |
| 1972 | """Generate .sln and .vcproj files. |
| 1973 | |
| 1974 | This is the entry point for this generator. |
| 1975 | Arguments: |
| 1976 | target_list: List of target pairs: 'base/base.gyp:base'. |
| 1977 | target_dicts: Dict of target properties keyed on target pair. |
| 1978 | data: Dictionary containing per .gyp data. |
| 1979 | """ |
| 1980 | global fixpath_prefix |
| 1981 | |
| 1982 | options = params['options'] |
| 1983 | |
| 1984 | # Get the project file format version back out of where we stashed it in |
| 1985 | # GeneratorCalculatedVariables. |
| 1986 | msvs_version = params['msvs_version'] |
| 1987 | |
| 1988 | generator_flags = params.get('generator_flags', {}) |
| 1989 | |
| 1990 | # Optionally shard targets marked with 'msvs_shard': SHARD_COUNT. |
| 1991 | (target_list, target_dicts) = MSVSUtil.ShardTargets(target_list, target_dicts) |
| 1992 | |
| 1993 | # Optionally use the large PDB workaround for targets marked with |
| 1994 | # 'msvs_large_pdb': 1. |
| 1995 | (target_list, target_dicts) = MSVSUtil.InsertLargePdbShims( |
| 1996 | target_list, target_dicts, generator_default_variables) |
| 1997 | |
| 1998 | # Optionally configure each spec to use ninja as the external builder. |
| 1999 | if params.get('flavor') == 'ninja': |
| 2000 | _InitNinjaFlavor(params, target_list, target_dicts) |
| 2001 | |
| 2002 | # Prepare the set of configurations. |
| 2003 | configs = set() |
| 2004 | for qualified_target in target_list: |
| 2005 | spec = target_dicts[qualified_target] |
| 2006 | for config_name, config in spec['configurations'].iteritems(): |
| 2007 | configs.add(_ConfigFullName(config_name, config)) |
| 2008 | configs = list(configs) |
| 2009 | |
| 2010 | # Figure out all the projects that will be generated and their guids |
| 2011 | project_objects = _CreateProjectObjects(target_list, target_dicts, options, |
| 2012 | msvs_version) |
| 2013 | |
| 2014 | # Generate each project. |
| 2015 | missing_sources = [] |
| 2016 | for project in project_objects.values(): |
| 2017 | fixpath_prefix = project.fixpath_prefix |
| 2018 | missing_sources.extend(_GenerateProject(project, options, msvs_version, |
| 2019 | generator_flags)) |
| 2020 | fixpath_prefix = None |
| 2021 | |
| 2022 | for build_file in data: |
| 2023 | # Validate build_file extension |
| 2024 | if not build_file.endswith('.gyp'): |
| 2025 | continue |
| 2026 | sln_path = os.path.splitext(build_file)[0] + options.suffix + '.sln' |
| 2027 | if options.generator_output: |
| 2028 | sln_path = os.path.join(options.generator_output, sln_path) |
| 2029 | # Get projects in the solution, and their dependents. |
| 2030 | sln_projects = gyp.common.BuildFileTargets(target_list, build_file) |
| 2031 | sln_projects += gyp.common.DeepDependencyTargets(target_dicts, sln_projects) |
| 2032 | # Create folder hierarchy. |
| 2033 | root_entries = _GatherSolutionFolders( |
| 2034 | sln_projects, project_objects, flat=msvs_version.FlatSolution()) |
| 2035 | # Create solution. |
| 2036 | sln = MSVSNew.MSVSSolution(sln_path, |
| 2037 | entries=root_entries, |
| 2038 | variants=configs, |
| 2039 | websiteProperties=False, |
| 2040 | version=msvs_version) |
| 2041 | sln.Write() |
| 2042 | |
| 2043 | if missing_sources: |
| 2044 | error_message = "Missing input files:\n" + \ |
| 2045 | '\n'.join(set(missing_sources)) |
| 2046 | if generator_flags.get('msvs_error_on_missing_sources', False): |
| 2047 | raise GypError(error_message) |
| 2048 | else: |
| 2049 | print >> sys.stdout, "Warning: " + error_message |
| 2050 | |
| 2051 | |
| 2052 | def _GenerateMSBuildFiltersFile(filters_path, source_files, |
| 2053 | rule_dependencies, extension_to_rule_name): |
| 2054 | """Generate the filters file. |
| 2055 | |
| 2056 | This file is used by Visual Studio to organize the presentation of source |
| 2057 | files into folders. |
| 2058 | |
| 2059 | Arguments: |
| 2060 | filters_path: The path of the file to be created. |
| 2061 | source_files: The hierarchical structure of all the sources. |
| 2062 | extension_to_rule_name: A dictionary mapping file extensions to rules. |
| 2063 | """ |
| 2064 | filter_group = [] |
| 2065 | source_group = [] |
| 2066 | _AppendFiltersForMSBuild('', source_files, rule_dependencies, |
| 2067 | extension_to_rule_name, filter_group, source_group) |
| 2068 | if filter_group: |
| 2069 | content = ['Project', |
| 2070 | {'ToolsVersion': '4.0', |
| 2071 | 'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003' |
| 2072 | }, |
| 2073 | ['ItemGroup'] + filter_group, |
| 2074 | ['ItemGroup'] + source_group |
| 2075 | ] |
| 2076 | easy_xml.WriteXmlIfChanged(content, filters_path, pretty=True, win32=True) |
| 2077 | elif os.path.exists(filters_path): |
| 2078 | # We don't need this filter anymore. Delete the old filter file. |
| 2079 | os.unlink(filters_path) |
| 2080 | |
| 2081 | |
| 2082 | def _AppendFiltersForMSBuild(parent_filter_name, sources, rule_dependencies, |
| 2083 | extension_to_rule_name, |
| 2084 | filter_group, source_group): |
| 2085 | """Creates the list of filters and sources to be added in the filter file. |
| 2086 | |
| 2087 | Args: |
| 2088 | parent_filter_name: The name of the filter under which the sources are |
| 2089 | found. |
| 2090 | sources: The hierarchy of filters and sources to process. |
| 2091 | extension_to_rule_name: A dictionary mapping file extensions to rules. |
| 2092 | filter_group: The list to which filter entries will be appended. |
| 2093 | source_group: The list to which source entries will be appeneded. |
| 2094 | """ |
| 2095 | for source in sources: |
| 2096 | if isinstance(source, MSVSProject.Filter): |
| 2097 | # We have a sub-filter. Create the name of that sub-filter. |
| 2098 | if not parent_filter_name: |
| 2099 | filter_name = source.name |
| 2100 | else: |
| 2101 | filter_name = '%s\\%s' % (parent_filter_name, source.name) |
| 2102 | # Add the filter to the group. |
| 2103 | filter_group.append( |
| 2104 | ['Filter', {'Include': filter_name}, |
| 2105 | ['UniqueIdentifier', MSVSNew.MakeGuid(source.name)]]) |
| 2106 | # Recurse and add its dependents. |
| 2107 | _AppendFiltersForMSBuild(filter_name, source.contents, |
| 2108 | rule_dependencies, extension_to_rule_name, |
| 2109 | filter_group, source_group) |
| 2110 | else: |
| 2111 | # It's a source. Create a source entry. |
| 2112 | _, element = _MapFileToMsBuildSourceType(source, rule_dependencies, |
| 2113 | extension_to_rule_name) |
| 2114 | source_entry = [element, {'Include': source}] |
| 2115 | # Specify the filter it is part of, if any. |
| 2116 | if parent_filter_name: |
| 2117 | source_entry.append(['Filter', parent_filter_name]) |
| 2118 | source_group.append(source_entry) |
| 2119 | |
| 2120 | |
| 2121 | def _MapFileToMsBuildSourceType(source, rule_dependencies, |
| 2122 | extension_to_rule_name): |
| 2123 | """Returns the group and element type of the source file. |
| 2124 | |
| 2125 | Arguments: |
| 2126 | source: The source file name. |
| 2127 | extension_to_rule_name: A dictionary mapping file extensions to rules. |
| 2128 | |
| 2129 | Returns: |
| 2130 | A pair of (group this file should be part of, the label of element) |
| 2131 | """ |
| 2132 | _, ext = os.path.splitext(source) |
| 2133 | if ext in extension_to_rule_name: |
| 2134 | group = 'rule' |
| 2135 | element = extension_to_rule_name[ext] |
| 2136 | elif ext in ['.cc', '.cpp', '.c', '.cxx']: |
| 2137 | group = 'compile' |
| 2138 | element = 'ClCompile' |
| 2139 | elif ext in ['.h', '.hxx']: |
| 2140 | group = 'include' |
| 2141 | element = 'ClInclude' |
| 2142 | elif ext == '.rc': |
| 2143 | group = 'resource' |
| 2144 | element = 'ResourceCompile' |
| 2145 | elif ext == '.asm': |
| 2146 | group = 'masm' |
| 2147 | element = 'MASM' |
| 2148 | elif ext == '.idl': |
| 2149 | group = 'midl' |
| 2150 | element = 'Midl' |
| 2151 | elif source in rule_dependencies: |
| 2152 | group = 'rule_dependency' |
| 2153 | element = 'CustomBuild' |
| 2154 | else: |
| 2155 | group = 'none' |
| 2156 | element = 'None' |
| 2157 | return (group, element) |
| 2158 | |
| 2159 | |
| 2160 | def _GenerateRulesForMSBuild(output_dir, options, spec, |
| 2161 | sources, excluded_sources, |
| 2162 | props_files_of_rules, targets_files_of_rules, |
| 2163 | actions_to_add, rule_dependencies, |
| 2164 | extension_to_rule_name): |
| 2165 | # MSBuild rules are implemented using three files: an XML file, a .targets |
| 2166 | # file and a .props file. |
| 2167 | # See http://blogs.msdn.com/b/vcblog/archive/2010/04/21/quick-help-on-vs2010-custom-build-rule.aspx |
| 2168 | # for more details. |
| 2169 | rules = spec.get('rules', []) |
| 2170 | rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))] |
| 2171 | rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))] |
| 2172 | |
| 2173 | msbuild_rules = [] |
| 2174 | for rule in rules_native: |
| 2175 | # Skip a rule with no action and no inputs. |
| 2176 | if 'action' not in rule and not rule.get('rule_sources', []): |
| 2177 | continue |
| 2178 | msbuild_rule = MSBuildRule(rule, spec) |
| 2179 | msbuild_rules.append(msbuild_rule) |
| 2180 | rule_dependencies.update(msbuild_rule.additional_dependencies.split(';')) |
| 2181 | extension_to_rule_name[msbuild_rule.extension] = msbuild_rule.rule_name |
| 2182 | if msbuild_rules: |
| 2183 | base = spec['target_name'] + options.suffix |
| 2184 | props_name = base + '.props' |
| 2185 | targets_name = base + '.targets' |
| 2186 | xml_name = base + '.xml' |
| 2187 | |
| 2188 | props_files_of_rules.add(props_name) |
| 2189 | targets_files_of_rules.add(targets_name) |
| 2190 | |
| 2191 | props_path = os.path.join(output_dir, props_name) |
| 2192 | targets_path = os.path.join(output_dir, targets_name) |
| 2193 | xml_path = os.path.join(output_dir, xml_name) |
| 2194 | |
| 2195 | _GenerateMSBuildRulePropsFile(props_path, msbuild_rules) |
| 2196 | _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules) |
| 2197 | _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules) |
| 2198 | |
| 2199 | if rules_external: |
| 2200 | _GenerateExternalRules(rules_external, output_dir, spec, |
| 2201 | sources, options, actions_to_add) |
| 2202 | _AdjustSourcesForRules(rules, sources, excluded_sources, True) |
| 2203 | |
| 2204 | |
| 2205 | class MSBuildRule(object): |
| 2206 | """Used to store information used to generate an MSBuild rule. |
| 2207 | |
| 2208 | Attributes: |
| 2209 | rule_name: The rule name, sanitized to use in XML. |
| 2210 | target_name: The name of the target. |
| 2211 | after_targets: The name of the AfterTargets element. |
| 2212 | before_targets: The name of the BeforeTargets element. |
| 2213 | depends_on: The name of the DependsOn element. |
| 2214 | compute_output: The name of the ComputeOutput element. |
| 2215 | dirs_to_make: The name of the DirsToMake element. |
| 2216 | inputs: The name of the _inputs element. |
| 2217 | tlog: The name of the _tlog element. |
| 2218 | extension: The extension this rule applies to. |
| 2219 | description: The message displayed when this rule is invoked. |
| 2220 | additional_dependencies: A string listing additional dependencies. |
| 2221 | outputs: The outputs of this rule. |
| 2222 | command: The command used to run the rule. |
| 2223 | """ |
| 2224 | |
| 2225 | def __init__(self, rule, spec): |
| 2226 | self.display_name = rule['rule_name'] |
| 2227 | # Assure that the rule name is only characters and numbers |
| 2228 | self.rule_name = re.sub(r'\W', '_', self.display_name) |
| 2229 | # Create the various element names, following the example set by the |
| 2230 | # Visual Studio 2008 to 2010 conversion. I don't know if VS2010 |
| 2231 | # is sensitive to the exact names. |
| 2232 | self.target_name = '_' + self.rule_name |
| 2233 | self.after_targets = self.rule_name + 'AfterTargets' |
| 2234 | self.before_targets = self.rule_name + 'BeforeTargets' |
| 2235 | self.depends_on = self.rule_name + 'DependsOn' |
| 2236 | self.compute_output = 'Compute%sOutput' % self.rule_name |
| 2237 | self.dirs_to_make = self.rule_name + 'DirsToMake' |
| 2238 | self.inputs = self.rule_name + '_inputs' |
| 2239 | self.tlog = self.rule_name + '_tlog' |
| 2240 | self.extension = rule['extension'] |
| 2241 | if not self.extension.startswith('.'): |
| 2242 | self.extension = '.' + self.extension |
| 2243 | |
| 2244 | self.description = MSVSSettings.ConvertVCMacrosToMSBuild( |
| 2245 | rule.get('message', self.rule_name)) |
| 2246 | old_additional_dependencies = _FixPaths(rule.get('inputs', [])) |
| 2247 | self.additional_dependencies = ( |
| 2248 | ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i) |
| 2249 | for i in old_additional_dependencies])) |
| 2250 | old_outputs = _FixPaths(rule.get('outputs', [])) |
| 2251 | self.outputs = ';'.join([MSVSSettings.ConvertVCMacrosToMSBuild(i) |
| 2252 | for i in old_outputs]) |
| 2253 | old_command = _BuildCommandLineForRule(spec, rule, has_input_path=True, |
| 2254 | do_setup_env=True) |
| 2255 | self.command = MSVSSettings.ConvertVCMacrosToMSBuild(old_command) |
| 2256 | |
| 2257 | |
| 2258 | def _GenerateMSBuildRulePropsFile(props_path, msbuild_rules): |
| 2259 | """Generate the .props file.""" |
| 2260 | content = ['Project', |
| 2261 | {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003'}] |
| 2262 | for rule in msbuild_rules: |
| 2263 | content.extend([ |
| 2264 | ['PropertyGroup', |
| 2265 | {'Condition': "'$(%s)' == '' and '$(%s)' == '' and " |
| 2266 | "'$(ConfigurationType)' != 'Makefile'" % (rule.before_targets, |
| 2267 | rule.after_targets) |
| 2268 | }, |
| 2269 | [rule.before_targets, 'Midl'], |
| 2270 | [rule.after_targets, 'CustomBuild'], |
| 2271 | ], |
| 2272 | ['PropertyGroup', |
| 2273 | [rule.depends_on, |
| 2274 | {'Condition': "'$(ConfigurationType)' != 'Makefile'"}, |
| 2275 | '_SelectedFiles;$(%s)' % rule.depends_on |
| 2276 | ], |
| 2277 | ], |
| 2278 | ['ItemDefinitionGroup', |
| 2279 | [rule.rule_name, |
| 2280 | ['CommandLineTemplate', rule.command], |
| 2281 | ['Outputs', rule.outputs], |
| 2282 | ['ExecutionDescription', rule.description], |
| 2283 | ['AdditionalDependencies', rule.additional_dependencies], |
| 2284 | ], |
| 2285 | ] |
| 2286 | ]) |
| 2287 | easy_xml.WriteXmlIfChanged(content, props_path, pretty=True, win32=True) |
| 2288 | |
| 2289 | |
| 2290 | def _GenerateMSBuildRuleTargetsFile(targets_path, msbuild_rules): |
| 2291 | """Generate the .targets file.""" |
| 2292 | content = ['Project', |
| 2293 | {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003' |
| 2294 | } |
| 2295 | ] |
| 2296 | item_group = [ |
| 2297 | 'ItemGroup', |
| 2298 | ['PropertyPageSchema', |
| 2299 | {'Include': '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'} |
| 2300 | ] |
| 2301 | ] |
| 2302 | for rule in msbuild_rules: |
| 2303 | item_group.append( |
| 2304 | ['AvailableItemName', |
| 2305 | {'Include': rule.rule_name}, |
| 2306 | ['Targets', rule.target_name], |
| 2307 | ]) |
| 2308 | content.append(item_group) |
| 2309 | |
| 2310 | for rule in msbuild_rules: |
| 2311 | content.append( |
| 2312 | ['UsingTask', |
| 2313 | {'TaskName': rule.rule_name, |
| 2314 | 'TaskFactory': 'XamlTaskFactory', |
| 2315 | 'AssemblyName': 'Microsoft.Build.Tasks.v4.0' |
| 2316 | }, |
| 2317 | ['Task', '$(MSBuildThisFileDirectory)$(MSBuildThisFileName).xml'], |
| 2318 | ]) |
| 2319 | for rule in msbuild_rules: |
| 2320 | rule_name = rule.rule_name |
| 2321 | target_outputs = '%%(%s.Outputs)' % rule_name |
| 2322 | target_inputs = ('%%(%s.Identity);%%(%s.AdditionalDependencies);' |
| 2323 | '$(MSBuildProjectFile)') % (rule_name, rule_name) |
| 2324 | rule_inputs = '%%(%s.Identity)' % rule_name |
| 2325 | extension_condition = ("'%(Extension)'=='.obj' or " |
| 2326 | "'%(Extension)'=='.res' or " |
| 2327 | "'%(Extension)'=='.rsc' or " |
| 2328 | "'%(Extension)'=='.lib'") |
| 2329 | remove_section = [ |
| 2330 | 'ItemGroup', |
| 2331 | {'Condition': "'@(SelectedFiles)' != ''"}, |
| 2332 | [rule_name, |
| 2333 | {'Remove': '@(%s)' % rule_name, |
| 2334 | 'Condition': "'%(Identity)' != '@(SelectedFiles)'" |
| 2335 | } |
| 2336 | ] |
| 2337 | ] |
| 2338 | inputs_section = [ |
| 2339 | 'ItemGroup', |
| 2340 | [rule.inputs, {'Include': '%%(%s.AdditionalDependencies)' % rule_name}] |
| 2341 | ] |
| 2342 | logging_section = [ |
| 2343 | 'ItemGroup', |
| 2344 | [rule.tlog, |
| 2345 | {'Include': '%%(%s.Outputs)' % rule_name, |
| 2346 | 'Condition': ("'%%(%s.Outputs)' != '' and " |
| 2347 | "'%%(%s.ExcludedFromBuild)' != 'true'" % |
| 2348 | (rule_name, rule_name)) |
| 2349 | }, |
| 2350 | ['Source', "@(%s, '|')" % rule_name], |
| 2351 | ['Inputs', "@(%s -> '%%(Fullpath)', ';')" % rule.inputs], |
| 2352 | ], |
| 2353 | ] |
| 2354 | message_section = [ |
| 2355 | 'Message', |
| 2356 | {'Importance': 'High', |
| 2357 | 'Text': '%%(%s.ExecutionDescription)' % rule_name |
| 2358 | } |
| 2359 | ] |
| 2360 | write_tlog_section = [ |
| 2361 | 'WriteLinesToFile', |
| 2362 | {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != " |
| 2363 | "'true'" % (rule.tlog, rule.tlog), |
| 2364 | 'File': '$(IntDir)$(ProjectName).write.1.tlog', |
| 2365 | 'Lines': "^%%(%s.Source);@(%s->'%%(Fullpath)')" % (rule.tlog, |
| 2366 | rule.tlog) |
| 2367 | } |
| 2368 | ] |
| 2369 | read_tlog_section = [ |
| 2370 | 'WriteLinesToFile', |
| 2371 | {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != " |
| 2372 | "'true'" % (rule.tlog, rule.tlog), |
| 2373 | 'File': '$(IntDir)$(ProjectName).read.1.tlog', |
| 2374 | 'Lines': "^%%(%s.Source);%%(%s.Inputs)" % (rule.tlog, rule.tlog) |
| 2375 | } |
| 2376 | ] |
| 2377 | command_and_input_section = [ |
| 2378 | rule_name, |
| 2379 | {'Condition': "'@(%s)' != '' and '%%(%s.ExcludedFromBuild)' != " |
| 2380 | "'true'" % (rule_name, rule_name), |
| 2381 | 'EchoOff': 'true', |
| 2382 | 'StandardOutputImportance': 'High', |
| 2383 | 'StandardErrorImportance': 'High', |
| 2384 | 'CommandLineTemplate': '%%(%s.CommandLineTemplate)' % rule_name, |
| 2385 | 'AdditionalOptions': '%%(%s.AdditionalOptions)' % rule_name, |
| 2386 | 'Inputs': rule_inputs |
| 2387 | } |
| 2388 | ] |
| 2389 | content.extend([ |
| 2390 | ['Target', |
| 2391 | {'Name': rule.target_name, |
| 2392 | 'BeforeTargets': '$(%s)' % rule.before_targets, |
| 2393 | 'AfterTargets': '$(%s)' % rule.after_targets, |
| 2394 | 'Condition': "'@(%s)' != ''" % rule_name, |
| 2395 | 'DependsOnTargets': '$(%s);%s' % (rule.depends_on, |
| 2396 | rule.compute_output), |
| 2397 | 'Outputs': target_outputs, |
| 2398 | 'Inputs': target_inputs |
| 2399 | }, |
| 2400 | remove_section, |
| 2401 | inputs_section, |
| 2402 | logging_section, |
| 2403 | message_section, |
| 2404 | write_tlog_section, |
| 2405 | read_tlog_section, |
| 2406 | command_and_input_section, |
| 2407 | ], |
| 2408 | ['PropertyGroup', |
| 2409 | ['ComputeLinkInputsTargets', |
| 2410 | '$(ComputeLinkInputsTargets);', |
| 2411 | '%s;' % rule.compute_output |
| 2412 | ], |
| 2413 | ['ComputeLibInputsTargets', |
| 2414 | '$(ComputeLibInputsTargets);', |
| 2415 | '%s;' % rule.compute_output |
| 2416 | ], |
| 2417 | ], |
| 2418 | ['Target', |
| 2419 | {'Name': rule.compute_output, |
| 2420 | 'Condition': "'@(%s)' != ''" % rule_name |
| 2421 | }, |
| 2422 | ['ItemGroup', |
| 2423 | [rule.dirs_to_make, |
| 2424 | {'Condition': "'@(%s)' != '' and " |
| 2425 | "'%%(%s.ExcludedFromBuild)' != 'true'" % (rule_name, rule_name), |
| 2426 | 'Include': '%%(%s.Outputs)' % rule_name |
| 2427 | } |
| 2428 | ], |
| 2429 | ['Link', |
| 2430 | {'Include': '%%(%s.Identity)' % rule.dirs_to_make, |
| 2431 | 'Condition': extension_condition |
| 2432 | } |
| 2433 | ], |
| 2434 | ['Lib', |
| 2435 | {'Include': '%%(%s.Identity)' % rule.dirs_to_make, |
| 2436 | 'Condition': extension_condition |
| 2437 | } |
| 2438 | ], |
| 2439 | ['ImpLib', |
| 2440 | {'Include': '%%(%s.Identity)' % rule.dirs_to_make, |
| 2441 | 'Condition': extension_condition |
| 2442 | } |
| 2443 | ], |
| 2444 | ], |
| 2445 | ['MakeDir', |
| 2446 | {'Directories': ("@(%s->'%%(RootDir)%%(Directory)')" % |
| 2447 | rule.dirs_to_make) |
| 2448 | } |
| 2449 | ] |
| 2450 | ], |
| 2451 | ]) |
| 2452 | easy_xml.WriteXmlIfChanged(content, targets_path, pretty=True, win32=True) |
| 2453 | |
| 2454 | |
| 2455 | def _GenerateMSBuildRuleXmlFile(xml_path, msbuild_rules): |
| 2456 | # Generate the .xml file |
| 2457 | content = [ |
| 2458 | 'ProjectSchemaDefinitions', |
| 2459 | {'xmlns': ('clr-namespace:Microsoft.Build.Framework.XamlTypes;' |
| 2460 | 'assembly=Microsoft.Build.Framework'), |
| 2461 | 'xmlns:x': 'http://schemas.microsoft.com/winfx/2006/xaml', |
| 2462 | 'xmlns:sys': 'clr-namespace:System;assembly=mscorlib', |
| 2463 | 'xmlns:transformCallback': |
| 2464 | 'Microsoft.Cpp.Dev10.ConvertPropertyCallback' |
| 2465 | } |
| 2466 | ] |
| 2467 | for rule in msbuild_rules: |
| 2468 | content.extend([ |
| 2469 | ['Rule', |
| 2470 | {'Name': rule.rule_name, |
| 2471 | 'PageTemplate': 'tool', |
| 2472 | 'DisplayName': rule.display_name, |
| 2473 | 'Order': '200' |
| 2474 | }, |
| 2475 | ['Rule.DataSource', |
| 2476 | ['DataSource', |
| 2477 | {'Persistence': 'ProjectFile', |
| 2478 | 'ItemType': rule.rule_name |
| 2479 | } |
| 2480 | ] |
| 2481 | ], |
| 2482 | ['Rule.Categories', |
| 2483 | ['Category', |
| 2484 | {'Name': 'General'}, |
| 2485 | ['Category.DisplayName', |
| 2486 | ['sys:String', 'General'], |
| 2487 | ], |
| 2488 | ], |
| 2489 | ['Category', |
| 2490 | {'Name': 'Command Line', |
| 2491 | 'Subtype': 'CommandLine' |
| 2492 | }, |
| 2493 | ['Category.DisplayName', |
| 2494 | ['sys:String', 'Command Line'], |
| 2495 | ], |
| 2496 | ], |
| 2497 | ], |
| 2498 | ['StringListProperty', |
| 2499 | {'Name': 'Inputs', |
| 2500 | 'Category': 'Command Line', |
| 2501 | 'IsRequired': 'true', |
| 2502 | 'Switch': ' ' |
| 2503 | }, |
| 2504 | ['StringListProperty.DataSource', |
| 2505 | ['DataSource', |
| 2506 | {'Persistence': 'ProjectFile', |
| 2507 | 'ItemType': rule.rule_name, |
| 2508 | 'SourceType': 'Item' |
| 2509 | } |
| 2510 | ] |
| 2511 | ], |
| 2512 | ], |
| 2513 | ['StringProperty', |
| 2514 | {'Name': 'CommandLineTemplate', |
| 2515 | 'DisplayName': 'Command Line', |
| 2516 | 'Visible': 'False', |
| 2517 | 'IncludeInCommandLine': 'False' |
| 2518 | } |
| 2519 | ], |
| 2520 | ['DynamicEnumProperty', |
| 2521 | {'Name': rule.before_targets, |
| 2522 | 'Category': 'General', |
| 2523 | 'EnumProvider': 'Targets', |
| 2524 | 'IncludeInCommandLine': 'False' |
| 2525 | }, |
| 2526 | ['DynamicEnumProperty.DisplayName', |
| 2527 | ['sys:String', 'Execute Before'], |
| 2528 | ], |
| 2529 | ['DynamicEnumProperty.Description', |
| 2530 | ['sys:String', 'Specifies the targets for the build customization' |
| 2531 | ' to run before.' |
| 2532 | ], |
| 2533 | ], |
| 2534 | ['DynamicEnumProperty.ProviderSettings', |
| 2535 | ['NameValuePair', |
| 2536 | {'Name': 'Exclude', |
| 2537 | 'Value': '^%s|^Compute' % rule.before_targets |
| 2538 | } |
| 2539 | ] |
| 2540 | ], |
| 2541 | ['DynamicEnumProperty.DataSource', |
| 2542 | ['DataSource', |
| 2543 | {'Persistence': 'ProjectFile', |
| 2544 | 'HasConfigurationCondition': 'true' |
| 2545 | } |
| 2546 | ] |
| 2547 | ], |
| 2548 | ], |
| 2549 | ['DynamicEnumProperty', |
| 2550 | {'Name': rule.after_targets, |
| 2551 | 'Category': 'General', |
| 2552 | 'EnumProvider': 'Targets', |
| 2553 | 'IncludeInCommandLine': 'False' |
| 2554 | }, |
| 2555 | ['DynamicEnumProperty.DisplayName', |
| 2556 | ['sys:String', 'Execute After'], |
| 2557 | ], |
| 2558 | ['DynamicEnumProperty.Description', |
| 2559 | ['sys:String', ('Specifies the targets for the build customization' |
| 2560 | ' to run after.') |
| 2561 | ], |
| 2562 | ], |
| 2563 | ['DynamicEnumProperty.ProviderSettings', |
| 2564 | ['NameValuePair', |
| 2565 | {'Name': 'Exclude', |
| 2566 | 'Value': '^%s|^Compute' % rule.after_targets |
| 2567 | } |
| 2568 | ] |
| 2569 | ], |
| 2570 | ['DynamicEnumProperty.DataSource', |
| 2571 | ['DataSource', |
| 2572 | {'Persistence': 'ProjectFile', |
| 2573 | 'ItemType': '', |
| 2574 | 'HasConfigurationCondition': 'true' |
| 2575 | } |
| 2576 | ] |
| 2577 | ], |
| 2578 | ], |
| 2579 | ['StringListProperty', |
| 2580 | {'Name': 'Outputs', |
| 2581 | 'DisplayName': 'Outputs', |
| 2582 | 'Visible': 'False', |
| 2583 | 'IncludeInCommandLine': 'False' |
| 2584 | } |
| 2585 | ], |
| 2586 | ['StringProperty', |
| 2587 | {'Name': 'ExecutionDescription', |
| 2588 | 'DisplayName': 'Execution Description', |
| 2589 | 'Visible': 'False', |
| 2590 | 'IncludeInCommandLine': 'False' |
| 2591 | } |
| 2592 | ], |
| 2593 | ['StringListProperty', |
| 2594 | {'Name': 'AdditionalDependencies', |
| 2595 | 'DisplayName': 'Additional Dependencies', |
| 2596 | 'IncludeInCommandLine': 'False', |
| 2597 | 'Visible': 'false' |
| 2598 | } |
| 2599 | ], |
| 2600 | ['StringProperty', |
| 2601 | {'Subtype': 'AdditionalOptions', |
| 2602 | 'Name': 'AdditionalOptions', |
| 2603 | 'Category': 'Command Line' |
| 2604 | }, |
| 2605 | ['StringProperty.DisplayName', |
| 2606 | ['sys:String', 'Additional Options'], |
| 2607 | ], |
| 2608 | ['StringProperty.Description', |
| 2609 | ['sys:String', 'Additional Options'], |
| 2610 | ], |
| 2611 | ], |
| 2612 | ], |
| 2613 | ['ItemType', |
| 2614 | {'Name': rule.rule_name, |
| 2615 | 'DisplayName': rule.display_name |
| 2616 | } |
| 2617 | ], |
| 2618 | ['FileExtension', |
| 2619 | {'Name': '*' + rule.extension, |
| 2620 | 'ContentType': rule.rule_name |
| 2621 | } |
| 2622 | ], |
| 2623 | ['ContentType', |
| 2624 | {'Name': rule.rule_name, |
| 2625 | 'DisplayName': '', |
| 2626 | 'ItemType': rule.rule_name |
| 2627 | } |
| 2628 | ] |
| 2629 | ]) |
| 2630 | easy_xml.WriteXmlIfChanged(content, xml_path, pretty=True, win32=True) |
| 2631 | |
| 2632 | |
| 2633 | def _GetConfigurationAndPlatform(name, settings): |
| 2634 | configuration = name.rsplit('_', 1)[0] |
| 2635 | platform = settings.get('msvs_configuration_platform', 'Win32') |
| 2636 | return (configuration, platform) |
| 2637 | |
| 2638 | |
| 2639 | def _GetConfigurationCondition(name, settings): |
| 2640 | return (r"'$(Configuration)|$(Platform)'=='%s|%s'" % |
| 2641 | _GetConfigurationAndPlatform(name, settings)) |
| 2642 | |
| 2643 | |
| 2644 | def _GetMSBuildProjectConfigurations(configurations): |
| 2645 | group = ['ItemGroup', {'Label': 'ProjectConfigurations'}] |
| 2646 | for (name, settings) in sorted(configurations.iteritems()): |
| 2647 | configuration, platform = _GetConfigurationAndPlatform(name, settings) |
| 2648 | designation = '%s|%s' % (configuration, platform) |
| 2649 | group.append( |
| 2650 | ['ProjectConfiguration', {'Include': designation}, |
| 2651 | ['Configuration', configuration], |
| 2652 | ['Platform', platform]]) |
| 2653 | return [group] |
| 2654 | |
| 2655 | |
| 2656 | def _GetMSBuildGlobalProperties(spec, guid, gyp_file_name): |
| 2657 | namespace = os.path.splitext(gyp_file_name)[0] |
| 2658 | properties = [ |
| 2659 | ['PropertyGroup', {'Label': 'Globals'}, |
| 2660 | ['ProjectGuid', guid], |
| 2661 | ['Keyword', 'Win32Proj'], |
| 2662 | ['RootNamespace', namespace], |
| 2663 | ['IgnoreWarnCompileDuplicatedFilename', 'true'], |
| 2664 | ] |
| 2665 | ] |
| 2666 | |
| 2667 | if os.environ.get('PROCESSOR_ARCHITECTURE') == 'AMD64' or \ |
| 2668 | os.environ.get('PROCESSOR_ARCHITEW6432') == 'AMD64': |
| 2669 | properties[0].append(['PreferredToolArchitecture', 'x64']) |
| 2670 | |
| 2671 | if spec.get('msvs_enable_winrt'): |
| 2672 | properties[0].append(['DefaultLanguage', 'en-US']) |
| 2673 | properties[0].append(['AppContainerApplication', 'true']) |
| 2674 | if spec.get('msvs_application_type_revision'): |
| 2675 | app_type_revision = spec.get('msvs_application_type_revision') |
| 2676 | properties[0].append(['ApplicationTypeRevision', app_type_revision]) |
| 2677 | else: |
| 2678 | properties[0].append(['ApplicationTypeRevision', '8.1']) |
| 2679 | |
| 2680 | if spec.get('msvs_target_platform_version'): |
| 2681 | target_platform_version = spec.get('msvs_target_platform_version') |
| 2682 | properties[0].append(['WindowsTargetPlatformVersion', |
| 2683 | target_platform_version]) |
| 2684 | if spec.get('msvs_target_platform_minversion'): |
| 2685 | target_platform_minversion = spec.get('msvs_target_platform_minversion') |
| 2686 | properties[0].append(['WindowsTargetPlatformMinVersion', |
| 2687 | target_platform_minversion]) |
| 2688 | else: |
| 2689 | properties[0].append(['WindowsTargetPlatformMinVersion', |
| 2690 | target_platform_version]) |
| 2691 | if spec.get('msvs_enable_winphone'): |
| 2692 | properties[0].append(['ApplicationType', 'Windows Phone']) |
| 2693 | else: |
| 2694 | properties[0].append(['ApplicationType', 'Windows Store']) |
| 2695 | |
| 2696 | platform_name = None |
| 2697 | msvs_windows_sdk_version = None |
| 2698 | for configuration in spec['configurations'].itervalues(): |
| 2699 | platform_name = platform_name or _ConfigPlatform(configuration) |
| 2700 | msvs_windows_sdk_version = (msvs_windows_sdk_version or |
| 2701 | _ConfigWindowsTargetPlatformVersion(configuration)) |
| 2702 | if platform_name and msvs_windows_sdk_version: |
| 2703 | break |
| 2704 | |
| 2705 | if platform_name == 'ARM': |
| 2706 | properties[0].append(['WindowsSDKDesktopARMSupport', 'true']) |
| 2707 | if msvs_windows_sdk_version: |
| 2708 | properties[0].append(['WindowsTargetPlatformVersion', |
| 2709 | str(msvs_windows_sdk_version)]) |
| 2710 | |
| 2711 | return properties |
| 2712 | |
| 2713 | def _GetMSBuildConfigurationDetails(spec, build_file): |
| 2714 | properties = {} |
| 2715 | for name, settings in spec['configurations'].iteritems(): |
| 2716 | msbuild_attributes = _GetMSBuildAttributes(spec, settings, build_file) |
| 2717 | condition = _GetConfigurationCondition(name, settings) |
| 2718 | character_set = msbuild_attributes.get('CharacterSet') |
| 2719 | _AddConditionalProperty(properties, condition, 'ConfigurationType', |
| 2720 | msbuild_attributes['ConfigurationType']) |
| 2721 | if character_set: |
| 2722 | if 'msvs_enable_winrt' not in spec : |
| 2723 | _AddConditionalProperty(properties, condition, 'CharacterSet', |
| 2724 | character_set) |
| 2725 | return _GetMSBuildPropertyGroup(spec, 'Configuration', properties) |
| 2726 | |
| 2727 | |
| 2728 | def _GetMSBuildLocalProperties(msbuild_toolset): |
| 2729 | # Currently the only local property we support is PlatformToolset |
| 2730 | properties = {} |
| 2731 | if msbuild_toolset: |
| 2732 | properties = [ |
| 2733 | ['PropertyGroup', {'Label': 'Locals'}, |
| 2734 | ['PlatformToolset', msbuild_toolset], |
| 2735 | ] |
| 2736 | ] |
| 2737 | return properties |
| 2738 | |
| 2739 | |
| 2740 | def _GetMSBuildPropertySheets(configurations): |
| 2741 | user_props = r'$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props' |
| 2742 | additional_props = {} |
| 2743 | props_specified = False |
| 2744 | for name, settings in sorted(configurations.iteritems()): |
| 2745 | configuration = _GetConfigurationCondition(name, settings) |
| 2746 | if settings.has_key('msbuild_props'): |
| 2747 | additional_props[configuration] = _FixPaths(settings['msbuild_props']) |
| 2748 | props_specified = True |
| 2749 | else: |
| 2750 | additional_props[configuration] = '' |
| 2751 | |
| 2752 | if not props_specified: |
| 2753 | return [ |
| 2754 | ['ImportGroup', |
| 2755 | {'Label': 'PropertySheets'}, |
| 2756 | ['Import', |
| 2757 | {'Project': user_props, |
| 2758 | 'Condition': "exists('%s')" % user_props, |
| 2759 | 'Label': 'LocalAppDataPlatform' |
| 2760 | } |
| 2761 | ] |
| 2762 | ] |
| 2763 | ] |
| 2764 | else: |
| 2765 | sheets = [] |
| 2766 | for condition, props in additional_props.iteritems(): |
| 2767 | import_group = [ |
| 2768 | 'ImportGroup', |
| 2769 | {'Label': 'PropertySheets', |
| 2770 | 'Condition': condition |
| 2771 | }, |
| 2772 | ['Import', |
| 2773 | {'Project': user_props, |
| 2774 | 'Condition': "exists('%s')" % user_props, |
| 2775 | 'Label': 'LocalAppDataPlatform' |
| 2776 | } |
| 2777 | ] |
| 2778 | ] |
| 2779 | for props_file in props: |
| 2780 | import_group.append(['Import', {'Project':props_file}]) |
| 2781 | sheets.append(import_group) |
| 2782 | return sheets |
| 2783 | |
| 2784 | def _ConvertMSVSBuildAttributes(spec, config, build_file): |
| 2785 | config_type = _GetMSVSConfigurationType(spec, build_file) |
| 2786 | msvs_attributes = _GetMSVSAttributes(spec, config, config_type) |
| 2787 | msbuild_attributes = {} |
| 2788 | for a in msvs_attributes: |
| 2789 | if a in ['IntermediateDirectory', 'OutputDirectory']: |
| 2790 | directory = MSVSSettings.ConvertVCMacrosToMSBuild(msvs_attributes[a]) |
| 2791 | if not directory.endswith('\\'): |
| 2792 | directory += '\\' |
| 2793 | msbuild_attributes[a] = directory |
| 2794 | elif a == 'CharacterSet': |
| 2795 | msbuild_attributes[a] = _ConvertMSVSCharacterSet(msvs_attributes[a]) |
| 2796 | elif a == 'ConfigurationType': |
| 2797 | msbuild_attributes[a] = _ConvertMSVSConfigurationType(msvs_attributes[a]) |
| 2798 | else: |
| 2799 | print 'Warning: Do not know how to convert MSVS attribute ' + a |
| 2800 | return msbuild_attributes |
| 2801 | |
| 2802 | |
| 2803 | def _ConvertMSVSCharacterSet(char_set): |
| 2804 | if char_set.isdigit(): |
| 2805 | char_set = { |
| 2806 | '0': 'MultiByte', |
| 2807 | '1': 'Unicode', |
| 2808 | '2': 'MultiByte', |
| 2809 | }[char_set] |
| 2810 | return char_set |
| 2811 | |
| 2812 | |
| 2813 | def _ConvertMSVSConfigurationType(config_type): |
| 2814 | if config_type.isdigit(): |
| 2815 | config_type = { |
| 2816 | '1': 'Application', |
| 2817 | '2': 'DynamicLibrary', |
| 2818 | '4': 'StaticLibrary', |
| 2819 | '10': 'Utility' |
| 2820 | }[config_type] |
| 2821 | return config_type |
| 2822 | |
| 2823 | |
| 2824 | def _GetMSBuildAttributes(spec, config, build_file): |
| 2825 | if 'msbuild_configuration_attributes' not in config: |
| 2826 | msbuild_attributes = _ConvertMSVSBuildAttributes(spec, config, build_file) |
| 2827 | |
| 2828 | else: |
| 2829 | config_type = _GetMSVSConfigurationType(spec, build_file) |
| 2830 | config_type = _ConvertMSVSConfigurationType(config_type) |
| 2831 | msbuild_attributes = config.get('msbuild_configuration_attributes', {}) |
| 2832 | msbuild_attributes.setdefault('ConfigurationType', config_type) |
| 2833 | output_dir = msbuild_attributes.get('OutputDirectory', |
| 2834 | '$(SolutionDir)$(Configuration)') |
| 2835 | msbuild_attributes['OutputDirectory'] = _FixPath(output_dir) + '\\' |
| 2836 | if 'IntermediateDirectory' not in msbuild_attributes: |
| 2837 | intermediate = _FixPath('$(Configuration)') + '\\' |
| 2838 | msbuild_attributes['IntermediateDirectory'] = intermediate |
| 2839 | if 'CharacterSet' in msbuild_attributes: |
| 2840 | msbuild_attributes['CharacterSet'] = _ConvertMSVSCharacterSet( |
| 2841 | msbuild_attributes['CharacterSet']) |
| 2842 | if 'TargetName' not in msbuild_attributes: |
| 2843 | prefix = spec.get('product_prefix', '') |
| 2844 | product_name = spec.get('product_name', '$(ProjectName)') |
| 2845 | target_name = prefix + product_name |
| 2846 | msbuild_attributes['TargetName'] = target_name |
| 2847 | |
| 2848 | if spec.get('msvs_external_builder'): |
| 2849 | external_out_dir = spec.get('msvs_external_builder_out_dir', '.') |
| 2850 | msbuild_attributes['OutputDirectory'] = _FixPath(external_out_dir) + '\\' |
| 2851 | |
| 2852 | # Make sure that 'TargetPath' matches 'Lib.OutputFile' or 'Link.OutputFile' |
| 2853 | # (depending on the tool used) to avoid MSB8012 warning. |
| 2854 | msbuild_tool_map = { |
| 2855 | 'executable': 'Link', |
| 2856 | 'shared_library': 'Link', |
| 2857 | 'loadable_module': 'Link', |
| 2858 | 'static_library': 'Lib', |
| 2859 | } |
| 2860 | msbuild_tool = msbuild_tool_map.get(spec['type']) |
| 2861 | if msbuild_tool: |
| 2862 | msbuild_settings = config['finalized_msbuild_settings'] |
| 2863 | out_file = msbuild_settings[msbuild_tool].get('OutputFile') |
| 2864 | if out_file: |
| 2865 | msbuild_attributes['TargetPath'] = _FixPath(out_file) |
| 2866 | target_ext = msbuild_settings[msbuild_tool].get('TargetExt') |
| 2867 | if target_ext: |
| 2868 | msbuild_attributes['TargetExt'] = target_ext |
| 2869 | |
| 2870 | return msbuild_attributes |
| 2871 | |
| 2872 | |
| 2873 | def _GetMSBuildConfigurationGlobalProperties(spec, configurations, build_file): |
| 2874 | # TODO(jeanluc) We could optimize out the following and do it only if |
| 2875 | # there are actions. |
| 2876 | # TODO(jeanluc) Handle the equivalent of setting 'CYGWIN=nontsec'. |
| 2877 | new_paths = [] |
| 2878 | cygwin_dirs = spec.get('msvs_cygwin_dirs', ['.'])[0] |
| 2879 | if cygwin_dirs: |
| 2880 | cyg_path = '$(MSBuildProjectDirectory)\\%s\\bin\\' % _FixPath(cygwin_dirs) |
| 2881 | new_paths.append(cyg_path) |
| 2882 | # TODO(jeanluc) Change the convention to have both a cygwin_dir and a |
| 2883 | # python_dir. |
| 2884 | python_path = cyg_path.replace('cygwin\\bin', 'python_26') |
| 2885 | new_paths.append(python_path) |
| 2886 | if new_paths: |
| 2887 | new_paths = '$(ExecutablePath);' + ';'.join(new_paths) |
| 2888 | |
| 2889 | properties = {} |
| 2890 | for (name, configuration) in sorted(configurations.iteritems()): |
| 2891 | condition = _GetConfigurationCondition(name, configuration) |
| 2892 | attributes = _GetMSBuildAttributes(spec, configuration, build_file) |
| 2893 | msbuild_settings = configuration['finalized_msbuild_settings'] |
| 2894 | _AddConditionalProperty(properties, condition, 'IntDir', |
| 2895 | attributes['IntermediateDirectory']) |
| 2896 | _AddConditionalProperty(properties, condition, 'OutDir', |
| 2897 | attributes['OutputDirectory']) |
| 2898 | _AddConditionalProperty(properties, condition, 'TargetName', |
| 2899 | attributes['TargetName']) |
| 2900 | |
| 2901 | if attributes.get('TargetPath'): |
| 2902 | _AddConditionalProperty(properties, condition, 'TargetPath', |
| 2903 | attributes['TargetPath']) |
| 2904 | if attributes.get('TargetExt'): |
| 2905 | _AddConditionalProperty(properties, condition, 'TargetExt', |
| 2906 | attributes['TargetExt']) |
| 2907 | |
| 2908 | if new_paths: |
| 2909 | _AddConditionalProperty(properties, condition, 'ExecutablePath', |
| 2910 | new_paths) |
| 2911 | tool_settings = msbuild_settings.get('', {}) |
| 2912 | for name, value in sorted(tool_settings.iteritems()): |
| 2913 | formatted_value = _GetValueFormattedForMSBuild('', name, value) |
| 2914 | _AddConditionalProperty(properties, condition, name, formatted_value) |
| 2915 | return _GetMSBuildPropertyGroup(spec, None, properties) |
| 2916 | |
| 2917 | |
| 2918 | def _AddConditionalProperty(properties, condition, name, value): |
| 2919 | """Adds a property / conditional value pair to a dictionary. |
| 2920 | |
| 2921 | Arguments: |
| 2922 | properties: The dictionary to be modified. The key is the name of the |
| 2923 | property. The value is itself a dictionary; its key is the value and |
| 2924 | the value a list of condition for which this value is true. |
| 2925 | condition: The condition under which the named property has the value. |
| 2926 | name: The name of the property. |
| 2927 | value: The value of the property. |
| 2928 | """ |
| 2929 | if name not in properties: |
| 2930 | properties[name] = {} |
| 2931 | values = properties[name] |
| 2932 | if value not in values: |
| 2933 | values[value] = [] |
| 2934 | conditions = values[value] |
| 2935 | conditions.append(condition) |
| 2936 | |
| 2937 | |
| 2938 | # Regex for msvs variable references ( i.e. $(FOO) ). |
| 2939 | MSVS_VARIABLE_REFERENCE = re.compile(r'\$\(([a-zA-Z_][a-zA-Z0-9_]*)\)') |
| 2940 | |
| 2941 | |
| 2942 | def _GetMSBuildPropertyGroup(spec, label, properties): |
| 2943 | """Returns a PropertyGroup definition for the specified properties. |
| 2944 | |
| 2945 | Arguments: |
| 2946 | spec: The target project dict. |
| 2947 | label: An optional label for the PropertyGroup. |
| 2948 | properties: The dictionary to be converted. The key is the name of the |
| 2949 | property. The value is itself a dictionary; its key is the value and |
| 2950 | the value a list of condition for which this value is true. |
| 2951 | """ |
| 2952 | group = ['PropertyGroup'] |
| 2953 | if label: |
| 2954 | group.append({'Label': label}) |
| 2955 | num_configurations = len(spec['configurations']) |
| 2956 | def GetEdges(node): |
| 2957 | # Use a definition of edges such that user_of_variable -> used_varible. |
| 2958 | # This happens to be easier in this case, since a variable's |
| 2959 | # definition contains all variables it references in a single string. |
| 2960 | edges = set() |
| 2961 | for value in sorted(properties[node].keys()): |
| 2962 | # Add to edges all $(...) references to variables. |
| 2963 | # |
| 2964 | # Variable references that refer to names not in properties are excluded |
| 2965 | # These can exist for instance to refer built in definitions like |
| 2966 | # $(SolutionDir). |
| 2967 | # |
| 2968 | # Self references are ignored. Self reference is used in a few places to |
| 2969 | # append to the default value. I.e. PATH=$(PATH);other_path |
| 2970 | edges.update(set([v for v in MSVS_VARIABLE_REFERENCE.findall(value) |
| 2971 | if v in properties and v != node])) |
| 2972 | return edges |
| 2973 | properties_ordered = gyp.common.TopologicallySorted( |
| 2974 | properties.keys(), GetEdges) |
| 2975 | # Walk properties in the reverse of a topological sort on |
| 2976 | # user_of_variable -> used_variable as this ensures variables are |
| 2977 | # defined before they are used. |
| 2978 | # NOTE: reverse(topsort(DAG)) = topsort(reverse_edges(DAG)) |
| 2979 | for name in reversed(properties_ordered): |
| 2980 | values = properties[name] |
| 2981 | for value, conditions in sorted(values.iteritems()): |
| 2982 | if len(conditions) == num_configurations: |
| 2983 | # If the value is the same all configurations, |
| 2984 | # just add one unconditional entry. |
| 2985 | group.append([name, value]) |
| 2986 | else: |
| 2987 | for condition in conditions: |
| 2988 | group.append([name, {'Condition': condition}, value]) |
| 2989 | return [group] |
| 2990 | |
| 2991 | |
| 2992 | def _GetMSBuildToolSettingsSections(spec, configurations): |
| 2993 | groups = [] |
| 2994 | for (name, configuration) in sorted(configurations.iteritems()): |
| 2995 | msbuild_settings = configuration['finalized_msbuild_settings'] |
| 2996 | group = ['ItemDefinitionGroup', |
| 2997 | {'Condition': _GetConfigurationCondition(name, configuration)} |
| 2998 | ] |
| 2999 | for tool_name, tool_settings in sorted(msbuild_settings.iteritems()): |
| 3000 | # Skip the tool named '' which is a holder of global settings handled |
| 3001 | # by _GetMSBuildConfigurationGlobalProperties. |
| 3002 | if tool_name: |
| 3003 | if tool_settings: |
| 3004 | tool = [tool_name] |
| 3005 | for name, value in sorted(tool_settings.iteritems()): |
| 3006 | formatted_value = _GetValueFormattedForMSBuild(tool_name, name, |
| 3007 | value) |
| 3008 | tool.append([name, formatted_value]) |
| 3009 | group.append(tool) |
| 3010 | groups.append(group) |
| 3011 | return groups |
| 3012 | |
| 3013 | |
| 3014 | def _FinalizeMSBuildSettings(spec, configuration): |
| 3015 | if 'msbuild_settings' in configuration: |
| 3016 | converted = False |
| 3017 | msbuild_settings = configuration['msbuild_settings'] |
| 3018 | MSVSSettings.ValidateMSBuildSettings(msbuild_settings) |
| 3019 | else: |
| 3020 | converted = True |
| 3021 | msvs_settings = configuration.get('msvs_settings', {}) |
| 3022 | msbuild_settings = MSVSSettings.ConvertToMSBuildSettings(msvs_settings) |
| 3023 | include_dirs, midl_include_dirs, resource_include_dirs = \ |
| 3024 | _GetIncludeDirs(configuration) |
| 3025 | libraries = _GetLibraries(spec) |
| 3026 | library_dirs = _GetLibraryDirs(configuration) |
| 3027 | out_file, _, msbuild_tool = _GetOutputFilePathAndTool(spec, msbuild=True) |
| 3028 | target_ext = _GetOutputTargetExt(spec) |
| 3029 | defines = _GetDefines(configuration) |
| 3030 | if converted: |
| 3031 | # Visual Studio 2010 has TR1 |
| 3032 | defines = [d for d in defines if d != '_HAS_TR1=0'] |
| 3033 | # Warn of ignored settings |
| 3034 | ignored_settings = ['msvs_tool_files'] |
| 3035 | for ignored_setting in ignored_settings: |
| 3036 | value = configuration.get(ignored_setting) |
| 3037 | if value: |
| 3038 | print ('Warning: The automatic conversion to MSBuild does not handle ' |
| 3039 | '%s. Ignoring setting of %s' % (ignored_setting, str(value))) |
| 3040 | |
| 3041 | defines = [_EscapeCppDefineForMSBuild(d) for d in defines] |
| 3042 | disabled_warnings = _GetDisabledWarnings(configuration) |
| 3043 | prebuild = configuration.get('msvs_prebuild') |
| 3044 | postbuild = configuration.get('msvs_postbuild') |
| 3045 | def_file = _GetModuleDefinition(spec) |
| 3046 | precompiled_header = configuration.get('msvs_precompiled_header') |
| 3047 | |
| 3048 | # Add the information to the appropriate tool |
| 3049 | # TODO(jeanluc) We could optimize and generate these settings only if |
| 3050 | # the corresponding files are found, e.g. don't generate ResourceCompile |
| 3051 | # if you don't have any resources. |
| 3052 | _ToolAppend(msbuild_settings, 'ClCompile', |
| 3053 | 'AdditionalIncludeDirectories', include_dirs) |
| 3054 | _ToolAppend(msbuild_settings, 'Midl', |
| 3055 | 'AdditionalIncludeDirectories', midl_include_dirs) |
| 3056 | _ToolAppend(msbuild_settings, 'ResourceCompile', |
| 3057 | 'AdditionalIncludeDirectories', resource_include_dirs) |
| 3058 | # Add in libraries, note that even for empty libraries, we want this |
| 3059 | # set, to prevent inheriting default libraries from the enviroment. |
| 3060 | _ToolSetOrAppend(msbuild_settings, 'Link', 'AdditionalDependencies', |
| 3061 | libraries) |
| 3062 | _ToolAppend(msbuild_settings, 'Link', 'AdditionalLibraryDirectories', |
| 3063 | library_dirs) |
| 3064 | if out_file: |
| 3065 | _ToolAppend(msbuild_settings, msbuild_tool, 'OutputFile', out_file, |
| 3066 | only_if_unset=True) |
| 3067 | if target_ext: |
| 3068 | _ToolAppend(msbuild_settings, msbuild_tool, 'TargetExt', target_ext, |
| 3069 | only_if_unset=True) |
| 3070 | # Add defines. |
| 3071 | _ToolAppend(msbuild_settings, 'ClCompile', |
| 3072 | 'PreprocessorDefinitions', defines) |
| 3073 | _ToolAppend(msbuild_settings, 'ResourceCompile', |
| 3074 | 'PreprocessorDefinitions', defines) |
| 3075 | # Add disabled warnings. |
| 3076 | _ToolAppend(msbuild_settings, 'ClCompile', |
| 3077 | 'DisableSpecificWarnings', disabled_warnings) |
| 3078 | # Turn on precompiled headers if appropriate. |
| 3079 | if precompiled_header: |
| 3080 | precompiled_header = os.path.split(precompiled_header)[1] |
| 3081 | _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'Use') |
| 3082 | _ToolAppend(msbuild_settings, 'ClCompile', |
| 3083 | 'PrecompiledHeaderFile', precompiled_header) |
| 3084 | _ToolAppend(msbuild_settings, 'ClCompile', |
| 3085 | 'ForcedIncludeFiles', [precompiled_header]) |
| 3086 | else: |
| 3087 | _ToolAppend(msbuild_settings, 'ClCompile', 'PrecompiledHeader', 'NotUsing') |
| 3088 | # Turn off WinRT compilation |
| 3089 | _ToolAppend(msbuild_settings, 'ClCompile', 'CompileAsWinRT', 'false') |
| 3090 | # Turn on import libraries if appropriate |
| 3091 | if spec.get('msvs_requires_importlibrary'): |
| 3092 | _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'false') |
| 3093 | # Loadable modules don't generate import libraries; |
| 3094 | # tell dependent projects to not expect one. |
| 3095 | if spec['type'] == 'loadable_module': |
| 3096 | _ToolAppend(msbuild_settings, '', 'IgnoreImportLibrary', 'true') |
| 3097 | # Set the module definition file if any. |
| 3098 | if def_file: |
| 3099 | _ToolAppend(msbuild_settings, 'Link', 'ModuleDefinitionFile', def_file) |
| 3100 | configuration['finalized_msbuild_settings'] = msbuild_settings |
| 3101 | if prebuild: |
| 3102 | _ToolAppend(msbuild_settings, 'PreBuildEvent', 'Command', prebuild) |
| 3103 | if postbuild: |
| 3104 | _ToolAppend(msbuild_settings, 'PostBuildEvent', 'Command', postbuild) |
| 3105 | |
| 3106 | |
| 3107 | def _GetValueFormattedForMSBuild(tool_name, name, value): |
| 3108 | if type(value) == list: |
| 3109 | # For some settings, VS2010 does not automatically extends the settings |
| 3110 | # TODO(jeanluc) Is this what we want? |
| 3111 | if name in ['AdditionalIncludeDirectories', |
| 3112 | 'AdditionalLibraryDirectories', |
| 3113 | 'AdditionalOptions', |
| 3114 | 'DelayLoadDLLs', |
| 3115 | 'DisableSpecificWarnings', |
| 3116 | 'PreprocessorDefinitions']: |
| 3117 | value.append('%%(%s)' % name) |
| 3118 | # For most tools, entries in a list should be separated with ';' but some |
| 3119 | # settings use a space. Check for those first. |
| 3120 | exceptions = { |
| 3121 | 'ClCompile': ['AdditionalOptions'], |
| 3122 | 'Link': ['AdditionalOptions'], |
| 3123 | 'Lib': ['AdditionalOptions']} |
| 3124 | if tool_name in exceptions and name in exceptions[tool_name]: |
| 3125 | char = ' ' |
| 3126 | else: |
| 3127 | char = ';' |
| 3128 | formatted_value = char.join( |
| 3129 | [MSVSSettings.ConvertVCMacrosToMSBuild(i) for i in value]) |
| 3130 | else: |
| 3131 | formatted_value = MSVSSettings.ConvertVCMacrosToMSBuild(value) |
| 3132 | return formatted_value |
| 3133 | |
| 3134 | |
| 3135 | def _VerifySourcesExist(sources, root_dir): |
| 3136 | """Verifies that all source files exist on disk. |
| 3137 | |
| 3138 | Checks that all regular source files, i.e. not created at run time, |
| 3139 | exist on disk. Missing files cause needless recompilation but no otherwise |
| 3140 | visible errors. |
| 3141 | |
| 3142 | Arguments: |
| 3143 | sources: A recursive list of Filter/file names. |
| 3144 | root_dir: The root directory for the relative path names. |
| 3145 | Returns: |
| 3146 | A list of source files that cannot be found on disk. |
| 3147 | """ |
| 3148 | missing_sources = [] |
| 3149 | for source in sources: |
| 3150 | if isinstance(source, MSVSProject.Filter): |
| 3151 | missing_sources.extend(_VerifySourcesExist(source.contents, root_dir)) |
| 3152 | else: |
| 3153 | if '$' not in source: |
| 3154 | full_path = os.path.join(root_dir, source) |
| 3155 | if not os.path.exists(full_path): |
| 3156 | missing_sources.append(full_path) |
| 3157 | return missing_sources |
| 3158 | |
| 3159 | |
| 3160 | def _GetMSBuildSources(spec, sources, exclusions, rule_dependencies, |
| 3161 | extension_to_rule_name, actions_spec, |
| 3162 | sources_handled_by_action, list_excluded): |
| 3163 | groups = ['none', 'masm', 'midl', 'include', 'compile', 'resource', 'rule', |
| 3164 | 'rule_dependency'] |
| 3165 | grouped_sources = {} |
| 3166 | for g in groups: |
| 3167 | grouped_sources[g] = [] |
| 3168 | |
| 3169 | _AddSources2(spec, sources, exclusions, grouped_sources, |
| 3170 | rule_dependencies, extension_to_rule_name, |
| 3171 | sources_handled_by_action, list_excluded) |
| 3172 | sources = [] |
| 3173 | for g in groups: |
| 3174 | if grouped_sources[g]: |
| 3175 | sources.append(['ItemGroup'] + grouped_sources[g]) |
| 3176 | if actions_spec: |
| 3177 | sources.append(['ItemGroup'] + actions_spec) |
| 3178 | return sources |
| 3179 | |
| 3180 | |
| 3181 | def _AddSources2(spec, sources, exclusions, grouped_sources, |
| 3182 | rule_dependencies, extension_to_rule_name, |
| 3183 | sources_handled_by_action, |
| 3184 | list_excluded): |
| 3185 | extensions_excluded_from_precompile = [] |
| 3186 | for source in sources: |
| 3187 | if isinstance(source, MSVSProject.Filter): |
| 3188 | _AddSources2(spec, source.contents, exclusions, grouped_sources, |
| 3189 | rule_dependencies, extension_to_rule_name, |
| 3190 | sources_handled_by_action, |
| 3191 | list_excluded) |
| 3192 | else: |
| 3193 | if not source in sources_handled_by_action: |
| 3194 | detail = [] |
| 3195 | excluded_configurations = exclusions.get(source, []) |
| 3196 | if len(excluded_configurations) == len(spec['configurations']): |
| 3197 | detail.append(['ExcludedFromBuild', 'true']) |
| 3198 | else: |
| 3199 | for config_name, configuration in sorted(excluded_configurations): |
| 3200 | condition = _GetConfigurationCondition(config_name, configuration) |
| 3201 | detail.append(['ExcludedFromBuild', |
| 3202 | {'Condition': condition}, |
| 3203 | 'true']) |
| 3204 | # Add precompile if needed |
| 3205 | for config_name, configuration in spec['configurations'].iteritems(): |
| 3206 | precompiled_source = configuration.get('msvs_precompiled_source', '') |
| 3207 | if precompiled_source != '': |
| 3208 | precompiled_source = _FixPath(precompiled_source) |
| 3209 | if not extensions_excluded_from_precompile: |
| 3210 | # If the precompiled header is generated by a C source, we must |
| 3211 | # not try to use it for C++ sources, and vice versa. |
| 3212 | basename, extension = os.path.splitext(precompiled_source) |
| 3213 | if extension == '.c': |
| 3214 | extensions_excluded_from_precompile = ['.cc', '.cpp', '.cxx'] |
| 3215 | else: |
| 3216 | extensions_excluded_from_precompile = ['.c'] |
| 3217 | |
| 3218 | if precompiled_source == source: |
| 3219 | condition = _GetConfigurationCondition(config_name, configuration) |
| 3220 | detail.append(['PrecompiledHeader', |
| 3221 | {'Condition': condition}, |
| 3222 | 'Create' |
| 3223 | ]) |
| 3224 | else: |
| 3225 | # Turn off precompiled header usage for source files of a |
| 3226 | # different type than the file that generated the |
| 3227 | # precompiled header. |
| 3228 | for extension in extensions_excluded_from_precompile: |
| 3229 | if source.endswith(extension): |
| 3230 | detail.append(['PrecompiledHeader', '']) |
| 3231 | detail.append(['ForcedIncludeFiles', '']) |
| 3232 | |
| 3233 | group, element = _MapFileToMsBuildSourceType(source, rule_dependencies, |
| 3234 | extension_to_rule_name) |
| 3235 | grouped_sources[group].append([element, {'Include': source}] + detail) |
| 3236 | |
| 3237 | |
| 3238 | def _GetMSBuildProjectReferences(project): |
| 3239 | references = [] |
| 3240 | if project.dependencies: |
| 3241 | group = ['ItemGroup'] |
| 3242 | for dependency in project.dependencies: |
| 3243 | guid = dependency.guid |
| 3244 | project_dir = os.path.split(project.path)[0] |
| 3245 | relative_path = gyp.common.RelativePath(dependency.path, project_dir) |
| 3246 | project_ref = ['ProjectReference', |
| 3247 | {'Include': relative_path}, |
| 3248 | ['Project', guid], |
| 3249 | ['ReferenceOutputAssembly', 'false'] |
| 3250 | ] |
| 3251 | for config in dependency.spec.get('configurations', {}).itervalues(): |
| 3252 | # If it's disabled in any config, turn it off in the reference. |
| 3253 | if config.get('msvs_2010_disable_uldi_when_referenced', 0): |
| 3254 | project_ref.append(['UseLibraryDependencyInputs', 'false']) |
| 3255 | break |
| 3256 | group.append(project_ref) |
| 3257 | references.append(group) |
| 3258 | return references |
| 3259 | |
| 3260 | |
| 3261 | def _GenerateMSBuildProject(project, options, version, generator_flags): |
| 3262 | spec = project.spec |
| 3263 | configurations = spec['configurations'] |
| 3264 | project_dir, project_file_name = os.path.split(project.path) |
| 3265 | gyp.common.EnsureDirExists(project.path) |
| 3266 | # Prepare list of sources and excluded sources. |
| 3267 | gyp_path = _NormalizedSource(project.build_file) |
| 3268 | relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir) |
| 3269 | |
| 3270 | gyp_file = os.path.split(project.build_file)[1] |
| 3271 | sources, excluded_sources = _PrepareListOfSources(spec, generator_flags, |
| 3272 | gyp_file) |
| 3273 | # Add rules. |
| 3274 | actions_to_add = {} |
| 3275 | props_files_of_rules = set() |
| 3276 | targets_files_of_rules = set() |
| 3277 | rule_dependencies = set() |
| 3278 | extension_to_rule_name = {} |
| 3279 | list_excluded = generator_flags.get('msvs_list_excluded_files', True) |
| 3280 | |
| 3281 | # Don't generate rules if we are using an external builder like ninja. |
| 3282 | if not spec.get('msvs_external_builder'): |
| 3283 | _GenerateRulesForMSBuild(project_dir, options, spec, |
| 3284 | sources, excluded_sources, |
| 3285 | props_files_of_rules, targets_files_of_rules, |
| 3286 | actions_to_add, rule_dependencies, |
| 3287 | extension_to_rule_name) |
| 3288 | else: |
| 3289 | rules = spec.get('rules', []) |
| 3290 | _AdjustSourcesForRules(rules, sources, excluded_sources, True) |
| 3291 | |
| 3292 | sources, excluded_sources, excluded_idl = ( |
| 3293 | _AdjustSourcesAndConvertToFilterHierarchy(spec, options, |
| 3294 | project_dir, sources, |
| 3295 | excluded_sources, |
| 3296 | list_excluded, version)) |
| 3297 | |
| 3298 | # Don't add actions if we are using an external builder like ninja. |
| 3299 | if not spec.get('msvs_external_builder'): |
| 3300 | _AddActions(actions_to_add, spec, project.build_file) |
| 3301 | _AddCopies(actions_to_add, spec) |
| 3302 | |
| 3303 | # NOTE: this stanza must appear after all actions have been decided. |
| 3304 | # Don't excluded sources with actions attached, or they won't run. |
| 3305 | excluded_sources = _FilterActionsFromExcluded( |
| 3306 | excluded_sources, actions_to_add) |
| 3307 | |
| 3308 | exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl) |
| 3309 | actions_spec, sources_handled_by_action = _GenerateActionsForMSBuild( |
| 3310 | spec, actions_to_add) |
| 3311 | |
| 3312 | _GenerateMSBuildFiltersFile(project.path + '.filters', sources, |
| 3313 | rule_dependencies, |
| 3314 | extension_to_rule_name) |
| 3315 | missing_sources = _VerifySourcesExist(sources, project_dir) |
| 3316 | |
| 3317 | for configuration in configurations.itervalues(): |
| 3318 | _FinalizeMSBuildSettings(spec, configuration) |
| 3319 | |
| 3320 | # Add attributes to root element |
| 3321 | |
| 3322 | import_default_section = [ |
| 3323 | ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.Default.props'}]] |
| 3324 | import_cpp_props_section = [ |
| 3325 | ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.props'}]] |
| 3326 | import_cpp_targets_section = [ |
| 3327 | ['Import', {'Project': r'$(VCTargetsPath)\Microsoft.Cpp.targets'}]] |
| 3328 | import_masm_props_section = [ |
| 3329 | ['Import', |
| 3330 | {'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.props'}]] |
| 3331 | import_masm_targets_section = [ |
| 3332 | ['Import', |
| 3333 | {'Project': r'$(VCTargetsPath)\BuildCustomizations\masm.targets'}]] |
| 3334 | macro_section = [['PropertyGroup', {'Label': 'UserMacros'}]] |
| 3335 | |
| 3336 | content = [ |
| 3337 | 'Project', |
| 3338 | {'xmlns': 'http://schemas.microsoft.com/developer/msbuild/2003', |
| 3339 | 'ToolsVersion': version.ProjectVersion(), |
| 3340 | 'DefaultTargets': 'Build' |
| 3341 | }] |
| 3342 | |
| 3343 | content += _GetMSBuildProjectConfigurations(configurations) |
| 3344 | content += _GetMSBuildGlobalProperties(spec, project.guid, project_file_name) |
| 3345 | content += import_default_section |
| 3346 | content += _GetMSBuildConfigurationDetails(spec, project.build_file) |
| 3347 | if spec.get('msvs_enable_winphone'): |
| 3348 | content += _GetMSBuildLocalProperties('v120_wp81') |
| 3349 | else: |
| 3350 | content += _GetMSBuildLocalProperties(project.msbuild_toolset) |
| 3351 | content += import_cpp_props_section |
| 3352 | content += import_masm_props_section |
| 3353 | content += _GetMSBuildExtensions(props_files_of_rules) |
| 3354 | content += _GetMSBuildPropertySheets(configurations) |
| 3355 | content += macro_section |
| 3356 | content += _GetMSBuildConfigurationGlobalProperties(spec, configurations, |
| 3357 | project.build_file) |
| 3358 | content += _GetMSBuildToolSettingsSections(spec, configurations) |
| 3359 | content += _GetMSBuildSources( |
| 3360 | spec, sources, exclusions, rule_dependencies, extension_to_rule_name, |
| 3361 | actions_spec, sources_handled_by_action, list_excluded) |
| 3362 | content += _GetMSBuildProjectReferences(project) |
| 3363 | content += import_cpp_targets_section |
| 3364 | content += import_masm_targets_section |
| 3365 | content += _GetMSBuildExtensionTargets(targets_files_of_rules) |
| 3366 | |
| 3367 | if spec.get('msvs_external_builder'): |
| 3368 | content += _GetMSBuildExternalBuilderTargets(spec) |
| 3369 | |
| 3370 | # TODO(jeanluc) File a bug to get rid of runas. We had in MSVS: |
| 3371 | # has_run_as = _WriteMSVSUserFile(project.path, version, spec) |
| 3372 | |
| 3373 | easy_xml.WriteXmlIfChanged(content, project.path, pretty=True, win32=True) |
| 3374 | |
| 3375 | return missing_sources |
| 3376 | |
| 3377 | |
| 3378 | def _GetMSBuildExternalBuilderTargets(spec): |
| 3379 | """Return a list of MSBuild targets for external builders. |
| 3380 | |
| 3381 | The "Build" and "Clean" targets are always generated. If the spec contains |
| 3382 | 'msvs_external_builder_clcompile_cmd', then the "ClCompile" target will also |
| 3383 | be generated, to support building selected C/C++ files. |
| 3384 | |
| 3385 | Arguments: |
| 3386 | spec: The gyp target spec. |
| 3387 | Returns: |
| 3388 | List of MSBuild 'Target' specs. |
| 3389 | """ |
| 3390 | build_cmd = _BuildCommandLineForRuleRaw( |
| 3391 | spec, spec['msvs_external_builder_build_cmd'], |
| 3392 | False, False, False, False) |
| 3393 | build_target = ['Target', {'Name': 'Build'}] |
| 3394 | build_target.append(['Exec', {'Command': build_cmd}]) |
| 3395 | |
| 3396 | clean_cmd = _BuildCommandLineForRuleRaw( |
| 3397 | spec, spec['msvs_external_builder_clean_cmd'], |
| 3398 | False, False, False, False) |
| 3399 | clean_target = ['Target', {'Name': 'Clean'}] |
| 3400 | clean_target.append(['Exec', {'Command': clean_cmd}]) |
| 3401 | |
| 3402 | targets = [build_target, clean_target] |
| 3403 | |
| 3404 | if spec.get('msvs_external_builder_clcompile_cmd'): |
| 3405 | clcompile_cmd = _BuildCommandLineForRuleRaw( |
| 3406 | spec, spec['msvs_external_builder_clcompile_cmd'], |
| 3407 | False, False, False, False) |
| 3408 | clcompile_target = ['Target', {'Name': 'ClCompile'}] |
| 3409 | clcompile_target.append(['Exec', {'Command': clcompile_cmd}]) |
| 3410 | targets.append(clcompile_target) |
| 3411 | |
| 3412 | return targets |
| 3413 | |
| 3414 | |
| 3415 | def _GetMSBuildExtensions(props_files_of_rules): |
| 3416 | extensions = ['ImportGroup', {'Label': 'ExtensionSettings'}] |
| 3417 | for props_file in props_files_of_rules: |
| 3418 | extensions.append(['Import', {'Project': props_file}]) |
| 3419 | return [extensions] |
| 3420 | |
| 3421 | |
| 3422 | def _GetMSBuildExtensionTargets(targets_files_of_rules): |
| 3423 | targets_node = ['ImportGroup', {'Label': 'ExtensionTargets'}] |
| 3424 | for targets_file in sorted(targets_files_of_rules): |
| 3425 | targets_node.append(['Import', {'Project': targets_file}]) |
| 3426 | return [targets_node] |
| 3427 | |
| 3428 | |
| 3429 | def _GenerateActionsForMSBuild(spec, actions_to_add): |
| 3430 | """Add actions accumulated into an actions_to_add, merging as needed. |
| 3431 | |
| 3432 | Arguments: |
| 3433 | spec: the target project dict |
| 3434 | actions_to_add: dictionary keyed on input name, which maps to a list of |
| 3435 | dicts describing the actions attached to that input file. |
| 3436 | |
| 3437 | Returns: |
| 3438 | A pair of (action specification, the sources handled by this action). |
| 3439 | """ |
| 3440 | sources_handled_by_action = OrderedSet() |
| 3441 | actions_spec = [] |
| 3442 | for primary_input, actions in actions_to_add.iteritems(): |
| 3443 | inputs = OrderedSet() |
| 3444 | outputs = OrderedSet() |
| 3445 | descriptions = [] |
| 3446 | commands = [] |
| 3447 | for action in actions: |
| 3448 | inputs.update(OrderedSet(action['inputs'])) |
| 3449 | outputs.update(OrderedSet(action['outputs'])) |
| 3450 | descriptions.append(action['description']) |
| 3451 | cmd = action['command'] |
| 3452 | # For most actions, add 'call' so that actions that invoke batch files |
| 3453 | # return and continue executing. msbuild_use_call provides a way to |
| 3454 | # disable this but I have not seen any adverse effect from doing that |
| 3455 | # for everything. |
| 3456 | if action.get('msbuild_use_call', True): |
| 3457 | cmd = 'call ' + cmd |
| 3458 | commands.append(cmd) |
| 3459 | # Add the custom build action for one input file. |
| 3460 | description = ', and also '.join(descriptions) |
| 3461 | |
| 3462 | # We can't join the commands simply with && because the command line will |
| 3463 | # get too long. See also _AddActions: cygwin's setup_env mustn't be called |
| 3464 | # for every invocation or the command that sets the PATH will grow too |
| 3465 | # long. |
| 3466 | command = '\r\n'.join([c + '\r\nif %errorlevel% neq 0 exit /b %errorlevel%' |
| 3467 | for c in commands]) |
| 3468 | _AddMSBuildAction(spec, |
| 3469 | primary_input, |
| 3470 | inputs, |
| 3471 | outputs, |
| 3472 | command, |
| 3473 | description, |
| 3474 | sources_handled_by_action, |
| 3475 | actions_spec) |
| 3476 | return actions_spec, sources_handled_by_action |
| 3477 | |
| 3478 | |
| 3479 | def _AddMSBuildAction(spec, primary_input, inputs, outputs, cmd, description, |
| 3480 | sources_handled_by_action, actions_spec): |
| 3481 | command = MSVSSettings.ConvertVCMacrosToMSBuild(cmd) |
| 3482 | primary_input = _FixPath(primary_input) |
| 3483 | inputs_array = _FixPaths(inputs) |
| 3484 | outputs_array = _FixPaths(outputs) |
| 3485 | additional_inputs = ';'.join([i for i in inputs_array |
| 3486 | if i != primary_input]) |
| 3487 | outputs = ';'.join(outputs_array) |
| 3488 | sources_handled_by_action.add(primary_input) |
| 3489 | action_spec = ['CustomBuild', {'Include': primary_input}] |
| 3490 | action_spec.extend( |
| 3491 | # TODO(jeanluc) 'Document' for all or just if as_sources? |
| 3492 | [['FileType', 'Document'], |
| 3493 | ['Command', command], |
| 3494 | ['Message', description], |
| 3495 | ['Outputs', outputs] |
| 3496 | ]) |
| 3497 | if additional_inputs: |
| 3498 | action_spec.append(['AdditionalInputs', additional_inputs]) |
| 3499 | actions_spec.append(action_spec) |