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