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