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