PageRenderTime 60ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 1ms

/media/webrtc/trunk/tools/gyp/pylib/gyp/generator/msvs.py

https://bitbucket.org/ehsan/broken-inbound
Python | 2935 lines | 2671 code | 97 blank | 167 comment | 85 complexity | 5009509d6968f0a0b503d96310315635 MD5 | raw file
Possible License(s): MPL-2.0-no-copyleft-exception, LGPL-3.0, AGPL-1.0, MIT, LGPL-2.1, 0BSD, BSD-2-Clause, MPL-2.0, BSD-3-Clause, Apache-2.0, GPL-2.0, JSON

Large files files are truncated, but you can click here to view the full file

  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. import copy
  5. import ntpath
  6. import os
  7. import posixpath
  8. import re
  9. import subprocess
  10. import sys
  11. import gyp.common
  12. import gyp.easy_xml as easy_xml
  13. import gyp.MSVSNew as MSVSNew
  14. import gyp.MSVSProject as MSVSProject
  15. import gyp.MSVSSettings as MSVSSettings
  16. import gyp.MSVSToolFile as MSVSToolFile
  17. import gyp.MSVSUserFile as MSVSUserFile
  18. import gyp.MSVSVersion as MSVSVersion
  19. # Regular expression for validating Visual Studio GUIDs. If the GUID
  20. # contains lowercase hex letters, MSVS will be fine. However,
  21. # IncrediBuild BuildConsole will parse the solution file, but then
  22. # silently skip building the target causing hard to track down errors.
  23. # Note that this only happens with the BuildConsole, and does not occur
  24. # if IncrediBuild is executed from inside Visual Studio. This regex
  25. # validates that the string looks like a GUID with all uppercase hex
  26. # letters.
  27. VALID_MSVS_GUID_CHARS = re.compile('^[A-F0-9\-]+$')
  28. generator_default_variables = {
  29. 'EXECUTABLE_PREFIX': '',
  30. 'EXECUTABLE_SUFFIX': '.exe',
  31. 'STATIC_LIB_PREFIX': '',
  32. 'SHARED_LIB_PREFIX': '',
  33. 'STATIC_LIB_SUFFIX': '.lib',
  34. 'SHARED_LIB_SUFFIX': '.dll',
  35. 'INTERMEDIATE_DIR': '$(IntDir)',
  36. 'SHARED_INTERMEDIATE_DIR': '$(OutDir)/obj/global_intermediate',
  37. 'OS': 'win',
  38. 'PRODUCT_DIR': '$(OutDir)',
  39. # TODO(jeanluc) The way we currently generate libraries makes Visual
  40. # Studio 2010 unhappy. We get a lot of warnings like:
  41. # warning MSB8012: TargetPath(...\Debug\gles2_c_lib.lib) does not match
  42. # the Library's OutputFile property value (...\Debug\lib\gles2_c_lib.lib).
  43. # This may cause your project to build incorrectly. To correct this,
  44. # please make sure that $(OutDir), $(TargetName) and $(TargetExt) property
  45. # values match the value specified in %(Lib.OutputFile).
  46. # Despite the warnings, this compile correctly. It would be nice to get rid
  47. # of the warnings.
  48. # TODO(jeanluc) I had: 'LIB_DIR': '$(OutDir)lib',
  49. 'LIB_DIR': '$(OutDir)/lib',
  50. 'RULE_INPUT_ROOT': '$(InputName)',
  51. 'RULE_INPUT_DIRNAME': '$(InputDir)',
  52. 'RULE_INPUT_EXT': '$(InputExt)',
  53. 'RULE_INPUT_NAME': '$(InputFileName)',
  54. 'RULE_INPUT_PATH': '$(InputPath)',
  55. 'CONFIGURATION_NAME': '$(ConfigurationName)',
  56. }
  57. # The msvs specific sections that hold paths
  58. generator_additional_path_sections = [
  59. 'msvs_cygwin_dirs',
  60. 'msvs_props',
  61. ]
  62. generator_additional_non_configuration_keys = [
  63. 'msvs_cygwin_dirs',
  64. 'msvs_cygwin_shell',
  65. 'msvs_shard',
  66. ]
  67. # List of precompiled header related keys.
  68. precomp_keys = [
  69. 'msvs_precompiled_header',
  70. 'msvs_precompiled_source',
  71. ]
  72. cached_username = None
  73. cached_domain = None
  74. # TODO(gspencer): Switch the os.environ calls to be
  75. # win32api.GetDomainName() and win32api.GetUserName() once the
  76. # python version in depot_tools has been updated to work on Vista
  77. # 64-bit.
  78. def _GetDomainAndUserName():
  79. if sys.platform not in ('win32', 'cygwin'):
  80. return ('DOMAIN', 'USERNAME')
  81. global cached_username
  82. global cached_domain
  83. if not cached_domain or not cached_username:
  84. domain = os.environ.get('USERDOMAIN')
  85. username = os.environ.get('USERNAME')
  86. if not domain or not username:
  87. call = subprocess.Popen(['net', 'config', 'Workstation'],
  88. stdout=subprocess.PIPE)
  89. config = call.communicate()[0]
  90. username_re = re.compile('^User name\s+(\S+)', re.MULTILINE)
  91. username_match = username_re.search(config)
  92. if username_match:
  93. username = username_match.group(1)
  94. domain_re = re.compile('^Logon domain\s+(\S+)', re.MULTILINE)
  95. domain_match = domain_re.search(config)
  96. if domain_match:
  97. domain = domain_match.group(1)
  98. cached_domain = domain
  99. cached_username = username
  100. return (cached_domain, cached_username)
  101. fixpath_prefix = None
  102. def _NormalizedSource(source):
  103. """Normalize the path.
  104. But not if that gets rid of a variable, as this may expand to something
  105. larger than one directory.
  106. Arguments:
  107. source: The path to be normalize.d
  108. Returns:
  109. The normalized path.
  110. """
  111. normalized = os.path.normpath(source)
  112. if source.count('$') == normalized.count('$'):
  113. source = normalized
  114. return source
  115. def _FixPath(path):
  116. """Convert paths to a form that will make sense in a vcproj file.
  117. Arguments:
  118. path: The path to convert, may contain / etc.
  119. Returns:
  120. The path with all slashes made into backslashes.
  121. """
  122. if fixpath_prefix and path and not os.path.isabs(path) and not path[0] == '$':
  123. path = os.path.join(fixpath_prefix, path)
  124. path = path.replace('/', '\\')
  125. path = _NormalizedSource(path)
  126. if path and path[-1] == '\\':
  127. path = path[:-1]
  128. return path
  129. def _FixPaths(paths):
  130. """Fix each of the paths of the list."""
  131. return [_FixPath(i) for i in paths]
  132. def _ConvertSourcesToFilterHierarchy(sources, prefix=None, excluded=None,
  133. list_excluded=True):
  134. """Converts a list split source file paths into a vcproj folder hierarchy.
  135. Arguments:
  136. sources: A list of source file paths split.
  137. prefix: A list of source file path layers meant to apply to each of sources.
  138. excluded: A set of excluded files.
  139. Returns:
  140. A hierarchy of filenames and MSVSProject.Filter objects that matches the
  141. layout of the source tree.
  142. For example:
  143. _ConvertSourcesToFilterHierarchy([['a', 'bob1.c'], ['b', 'bob2.c']],
  144. prefix=['joe'])
  145. -->
  146. [MSVSProject.Filter('a', contents=['joe\\a\\bob1.c']),
  147. MSVSProject.Filter('b', contents=['joe\\b\\bob2.c'])]
  148. """
  149. if not prefix: prefix = []
  150. result = []
  151. excluded_result = []
  152. folders = dict()
  153. # Gather files into the final result, excluded, or folders.
  154. for s in sources:
  155. if len(s) == 1:
  156. filename = _NormalizedSource('\\'.join(prefix + s))
  157. if filename in excluded:
  158. excluded_result.append(filename)
  159. else:
  160. result.append(filename)
  161. else:
  162. if not folders.get(s[0]):
  163. folders[s[0]] = []
  164. folders[s[0]].append(s[1:])
  165. # Add a folder for excluded files.
  166. if excluded_result and list_excluded:
  167. excluded_folder = MSVSProject.Filter('_excluded_files',
  168. contents=excluded_result)
  169. result.append(excluded_folder)
  170. # Populate all the folders.
  171. for f in folders:
  172. contents = _ConvertSourcesToFilterHierarchy(folders[f], prefix=prefix + [f],
  173. excluded=excluded,
  174. list_excluded=list_excluded)
  175. contents = MSVSProject.Filter(f, contents=contents)
  176. result.append(contents)
  177. return result
  178. def _ToolAppend(tools, tool_name, setting, value, only_if_unset=False):
  179. if not value: return
  180. # TODO(bradnelson): ugly hack, fix this more generally!!!
  181. if 'Directories' in setting or 'Dependencies' in setting:
  182. if type(value) == str:
  183. value = value.replace('/', '\\')
  184. else:
  185. value = [i.replace('/', '\\') for i in value]
  186. if not tools.get(tool_name):
  187. tools[tool_name] = dict()
  188. tool = tools[tool_name]
  189. if tool.get(setting):
  190. if only_if_unset: return
  191. if type(tool[setting]) == list:
  192. tool[setting] += value
  193. else:
  194. raise TypeError(
  195. 'Appending "%s" to a non-list setting "%s" for tool "%s" is '
  196. 'not allowed, previous value: %s' % (
  197. value, setting, tool_name, str(tool[setting])))
  198. else:
  199. tool[setting] = value
  200. def _ConfigPlatform(config_data):
  201. return config_data.get('msvs_configuration_platform', 'Win32')
  202. def _ConfigBaseName(config_name, platform_name):
  203. if config_name.endswith('_' + platform_name):
  204. return config_name[0:-len(platform_name)-1]
  205. else:
  206. return config_name
  207. def _ConfigFullName(config_name, config_data):
  208. platform_name = _ConfigPlatform(config_data)
  209. return '%s|%s' % (_ConfigBaseName(config_name, platform_name), platform_name)
  210. def _BuildCommandLineForRuleRaw(spec, cmd, cygwin_shell, has_input_path,
  211. quote_cmd):
  212. if [x for x in cmd if '$(InputDir)' in x]:
  213. input_dir_preamble = (
  214. 'set INPUTDIR=$(InputDir)\n'
  215. 'set INPUTDIR=%INPUTDIR:$(ProjectDir)=%\n'
  216. 'set INPUTDIR=%INPUTDIR:~0,-1%\n'
  217. )
  218. else:
  219. input_dir_preamble = ''
  220. if cygwin_shell:
  221. # Find path to cygwin.
  222. cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
  223. # Prepare command.
  224. direct_cmd = cmd
  225. direct_cmd = [i.replace('$(IntDir)',
  226. '`cygpath -m "${INTDIR}"`') for i in direct_cmd]
  227. direct_cmd = [i.replace('$(OutDir)',
  228. '`cygpath -m "${OUTDIR}"`') for i in direct_cmd]
  229. direct_cmd = [i.replace('$(InputDir)',
  230. '`cygpath -m "${INPUTDIR}"`') for i in direct_cmd]
  231. if has_input_path:
  232. direct_cmd = [i.replace('$(InputPath)',
  233. '`cygpath -m "${INPUTPATH}"`')
  234. for i in direct_cmd]
  235. direct_cmd = ['"%s"' % i for i in direct_cmd]
  236. direct_cmd = [i.replace('"', '\\"') for i in direct_cmd]
  237. #direct_cmd = gyp.common.EncodePOSIXShellList(direct_cmd)
  238. direct_cmd = ' '.join(direct_cmd)
  239. # TODO(quote): regularize quoting path names throughout the module
  240. cmd = (
  241. 'call "$(ProjectDir)%(cygwin_dir)s\\setup_env.bat" && '
  242. 'set CYGWIN=nontsec&& ')
  243. if direct_cmd.find('NUMBER_OF_PROCESSORS') >= 0:
  244. cmd += 'set /a NUMBER_OF_PROCESSORS_PLUS_1=%%NUMBER_OF_PROCESSORS%%+1&& '
  245. if direct_cmd.find('INTDIR') >= 0:
  246. cmd += 'set INTDIR=$(IntDir)&& '
  247. if direct_cmd.find('OUTDIR') >= 0:
  248. cmd += 'set OUTDIR=$(OutDir)&& '
  249. if has_input_path and direct_cmd.find('INPUTPATH') >= 0:
  250. cmd += 'set INPUTPATH=$(InputPath) && '
  251. cmd += 'bash -c "%(cmd)s"'
  252. cmd = cmd % {'cygwin_dir': cygwin_dir,
  253. 'cmd': direct_cmd}
  254. return input_dir_preamble + cmd
  255. else:
  256. # Convert cat --> type to mimic unix.
  257. if cmd[0] == 'cat':
  258. command = ['type']
  259. else:
  260. command = [cmd[0].replace('/', '\\')]
  261. # Fix the paths
  262. # If the argument starts with a slash, it's probably a command line switch
  263. arguments = [i.startswith('/') and i or _FixPath(i) for i in cmd[1:]]
  264. arguments = [i.replace('$(InputDir)','%INPUTDIR%') for i in arguments]
  265. if quote_cmd:
  266. # Support a mode for using cmd directly.
  267. # Convert any paths to native form (first element is used directly).
  268. # TODO(quote): regularize quoting path names throughout the module
  269. arguments = ['"%s"' % i for i in arguments]
  270. # Collapse into a single command.
  271. return input_dir_preamble + ' '.join(command + arguments)
  272. def _BuildCommandLineForRule(spec, rule, has_input_path):
  273. # Find path to cygwin.
  274. cygwin_dir = _FixPath(spec.get('msvs_cygwin_dirs', ['.'])[0])
  275. # Currently this weird argument munging is used to duplicate the way a
  276. # python script would need to be run as part of the chrome tree.
  277. # Eventually we should add some sort of rule_default option to set this
  278. # per project. For now the behavior chrome needs is the default.
  279. mcs = rule.get('msvs_cygwin_shell')
  280. if mcs is None:
  281. mcs = int(spec.get('msvs_cygwin_shell', 1))
  282. elif isinstance(mcs, str):
  283. mcs = int(mcs)
  284. quote_cmd = int(rule.get('msvs_quote_cmd', 1))
  285. return _BuildCommandLineForRuleRaw(spec, rule['action'], mcs, has_input_path,
  286. quote_cmd)
  287. def _AddActionStep(actions_dict, inputs, outputs, description, command):
  288. """Merge action into an existing list of actions.
  289. Care must be taken so that actions which have overlapping inputs either don't
  290. get assigned to the same input, or get collapsed into one.
  291. Arguments:
  292. actions_dict: dictionary keyed on input name, which maps to a list of
  293. dicts describing the actions attached to that input file.
  294. inputs: list of inputs
  295. outputs: list of outputs
  296. description: description of the action
  297. command: command line to execute
  298. """
  299. # Require there to be at least one input (call sites will ensure this).
  300. assert inputs
  301. action = {
  302. 'inputs': inputs,
  303. 'outputs': outputs,
  304. 'description': description,
  305. 'command': command,
  306. }
  307. # Pick where to stick this action.
  308. # While less than optimal in terms of build time, attach them to the first
  309. # input for now.
  310. chosen_input = inputs[0]
  311. # Add it there.
  312. if chosen_input not in actions_dict:
  313. actions_dict[chosen_input] = []
  314. actions_dict[chosen_input].append(action)
  315. def _AddCustomBuildToolForMSVS(p, spec, primary_input,
  316. inputs, outputs, description, cmd):
  317. """Add a custom build tool to execute something.
  318. Arguments:
  319. p: the target project
  320. spec: the target project dict
  321. primary_input: input file to attach the build tool to
  322. inputs: list of inputs
  323. outputs: list of outputs
  324. description: description of the action
  325. cmd: command line to execute
  326. """
  327. inputs = _FixPaths(inputs)
  328. outputs = _FixPaths(outputs)
  329. tool = MSVSProject.Tool(
  330. 'VCCustomBuildTool',
  331. {'Description': description,
  332. 'AdditionalDependencies': ';'.join(inputs),
  333. 'Outputs': ';'.join(outputs),
  334. 'CommandLine': cmd,
  335. })
  336. # Add to the properties of primary input for each config.
  337. for config_name, c_data in spec['configurations'].iteritems():
  338. p.AddFileConfig(_FixPath(primary_input),
  339. _ConfigFullName(config_name, c_data), tools=[tool])
  340. def _AddAccumulatedActionsToMSVS(p, spec, actions_dict):
  341. """Add actions accumulated into an actions_dict, merging as needed.
  342. Arguments:
  343. p: the target project
  344. spec: the target project dict
  345. actions_dict: dictionary keyed on input name, which maps to a list of
  346. dicts describing the actions attached to that input file.
  347. """
  348. for primary_input in actions_dict:
  349. inputs = set()
  350. outputs = set()
  351. descriptions = []
  352. commands = []
  353. for action in actions_dict[primary_input]:
  354. inputs.update(set(action['inputs']))
  355. outputs.update(set(action['outputs']))
  356. descriptions.append(action['description'])
  357. commands.append(action['command'])
  358. # Add the custom build step for one input file.
  359. description = ', and also '.join(descriptions)
  360. command = '\r\n'.join(commands)
  361. _AddCustomBuildToolForMSVS(p, spec,
  362. primary_input=primary_input,
  363. inputs=inputs,
  364. outputs=outputs,
  365. description=description,
  366. cmd=command)
  367. def _RuleExpandPath(path, input_file):
  368. """Given the input file to which a rule applied, string substitute a path.
  369. Arguments:
  370. path: a path to string expand
  371. input_file: the file to which the rule applied.
  372. Returns:
  373. The string substituted path.
  374. """
  375. path = path.replace('$(InputName)',
  376. os.path.splitext(os.path.split(input_file)[1])[0])
  377. path = path.replace('$(InputDir)', os.path.dirname(input_file))
  378. path = path.replace('$(InputExt)',
  379. os.path.splitext(os.path.split(input_file)[1])[1])
  380. path = path.replace('$(InputFileName)', os.path.split(input_file)[1])
  381. path = path.replace('$(InputPath)', input_file)
  382. return path
  383. def _FindRuleTriggerFiles(rule, sources):
  384. """Find the list of files which a particular rule applies to.
  385. Arguments:
  386. rule: the rule in question
  387. sources: the set of all known source files for this project
  388. Returns:
  389. The list of sources that trigger a particular rule.
  390. """
  391. rule_ext = rule['extension']
  392. return [s for s in sources if s.endswith('.' + rule_ext)]
  393. def _RuleInputsAndOutputs(rule, trigger_file):
  394. """Find the inputs and outputs generated by a rule.
  395. Arguments:
  396. rule: the rule in question.
  397. trigger_file: the main trigger for this rule.
  398. Returns:
  399. The pair of (inputs, outputs) involved in this rule.
  400. """
  401. raw_inputs = _FixPaths(rule.get('inputs', []))
  402. raw_outputs = _FixPaths(rule.get('outputs', []))
  403. inputs = set()
  404. outputs = set()
  405. inputs.add(trigger_file)
  406. for i in raw_inputs:
  407. inputs.add(_RuleExpandPath(i, trigger_file))
  408. for o in raw_outputs:
  409. outputs.add(_RuleExpandPath(o, trigger_file))
  410. return (inputs, outputs)
  411. def _GenerateNativeRulesForMSVS(p, rules, output_dir, spec, options):
  412. """Generate a native rules file.
  413. Arguments:
  414. p: the target project
  415. rules: the set of rules to include
  416. output_dir: the directory in which the project/gyp resides
  417. spec: the project dict
  418. options: global generator options
  419. """
  420. rules_filename = '%s%s.rules' % (spec['target_name'],
  421. options.suffix)
  422. rules_file = MSVSToolFile.Writer(os.path.join(output_dir, rules_filename),
  423. spec['target_name'])
  424. # Add each rule.
  425. for r in rules:
  426. rule_name = r['rule_name']
  427. rule_ext = r['extension']
  428. inputs = _FixPaths(r.get('inputs', []))
  429. outputs = _FixPaths(r.get('outputs', []))
  430. cmd = _BuildCommandLineForRule(spec, r, has_input_path=True)
  431. rules_file.AddCustomBuildRule(name=rule_name,
  432. description=r.get('message', rule_name),
  433. extensions=[rule_ext],
  434. additional_dependencies=inputs,
  435. outputs=outputs,
  436. cmd=cmd)
  437. # Write out rules file.
  438. rules_file.WriteIfChanged()
  439. # Add rules file to project.
  440. p.AddToolFile(rules_filename)
  441. def _Cygwinify(path):
  442. path = path.replace('$(OutDir)', '$(OutDirCygwin)')
  443. path = path.replace('$(IntDir)', '$(IntDirCygwin)')
  444. return path
  445. def _GenerateExternalRules(rules, output_dir, spec,
  446. sources, options, actions_to_add):
  447. """Generate an external makefile to do a set of rules.
  448. Arguments:
  449. rules: the list of rules to include
  450. output_dir: path containing project and gyp files
  451. spec: project specification data
  452. sources: set of sources known
  453. options: global generator options
  454. actions_to_add: The list of actions we will add to.
  455. """
  456. filename = '%s_rules%s.mk' % (spec['target_name'], options.suffix)
  457. mk_file = gyp.common.WriteOnDiff(os.path.join(output_dir, filename))
  458. # Find cygwin style versions of some paths.
  459. mk_file.write('OutDirCygwin:=$(shell cygpath -u "$(OutDir)")\n')
  460. mk_file.write('IntDirCygwin:=$(shell cygpath -u "$(IntDir)")\n')
  461. # Gather stuff needed to emit all: target.
  462. all_inputs = set()
  463. all_outputs = set()
  464. all_output_dirs = set()
  465. first_outputs = []
  466. for rule in rules:
  467. trigger_files = _FindRuleTriggerFiles(rule, sources)
  468. for tf in trigger_files:
  469. inputs, outputs = _RuleInputsAndOutputs(rule, tf)
  470. all_inputs.update(set(inputs))
  471. all_outputs.update(set(outputs))
  472. # Only use one target from each rule as the dependency for
  473. # 'all' so we don't try to build each rule multiple times.
  474. first_outputs.append(list(outputs)[0])
  475. # Get the unique output directories for this rule.
  476. output_dirs = [os.path.split(i)[0] for i in outputs]
  477. for od in output_dirs:
  478. all_output_dirs.add(od)
  479. first_outputs_cyg = [_Cygwinify(i) for i in first_outputs]
  480. # Write out all: target, including mkdir for each output directory.
  481. mk_file.write('all: %s\n' % ' '.join(first_outputs_cyg))
  482. for od in all_output_dirs:
  483. if od:
  484. mk_file.write('\tmkdir -p `cygpath -u "%s"`\n' % od)
  485. mk_file.write('\n')
  486. # Define how each output is generated.
  487. for rule in rules:
  488. trigger_files = _FindRuleTriggerFiles(rule, sources)
  489. for tf in trigger_files:
  490. # Get all the inputs and outputs for this rule for this trigger file.
  491. inputs, outputs = _RuleInputsAndOutputs(rule, tf)
  492. inputs = [_Cygwinify(i) for i in inputs]
  493. outputs = [_Cygwinify(i) for i in outputs]
  494. # Prepare the command line for this rule.
  495. cmd = [_RuleExpandPath(c, tf) for c in rule['action']]
  496. cmd = ['"%s"' % i for i in cmd]
  497. cmd = ' '.join(cmd)
  498. # Add it to the makefile.
  499. mk_file.write('%s: %s\n' % (' '.join(outputs), ' '.join(inputs)))
  500. mk_file.write('\t%s\n\n' % cmd)
  501. # Close up the file.
  502. mk_file.close()
  503. # Add makefile to list of sources.
  504. sources.add(filename)
  505. # Add a build action to call makefile.
  506. cmd = ['make',
  507. 'OutDir=$(OutDir)',
  508. 'IntDir=$(IntDir)',
  509. '-j', '${NUMBER_OF_PROCESSORS_PLUS_1}',
  510. '-f', filename]
  511. cmd = _BuildCommandLineForRuleRaw(spec, cmd, True, False, True)
  512. # Insert makefile as 0'th input, so it gets the action attached there,
  513. # as this is easier to understand from in the IDE.
  514. all_inputs = list(all_inputs)
  515. all_inputs.insert(0, filename)
  516. _AddActionStep(actions_to_add,
  517. inputs=_FixPaths(all_inputs),
  518. outputs=_FixPaths(all_outputs),
  519. description='Running %s' % cmd,
  520. command=cmd)
  521. def _EscapeEnvironmentVariableExpansion(s):
  522. """Escapes % characters.
  523. Escapes any % characters so that Windows-style environment variable
  524. expansions will leave them alone.
  525. See http://connect.microsoft.com/VisualStudio/feedback/details/106127/cl-d-name-text-containing-percentage-characters-doesnt-compile
  526. to understand why we have to do this.
  527. Args:
  528. s: The string to be escaped.
  529. Returns:
  530. The escaped string.
  531. """
  532. s = s.replace('%', '%%')
  533. return s
  534. quote_replacer_regex = re.compile(r'(\\*)"')
  535. def _EscapeCommandLineArgumentForMSVS(s):
  536. """Escapes a Windows command-line argument.
  537. So that the Win32 CommandLineToArgv function will turn the escaped result back
  538. into the original string.
  539. See http://msdn.microsoft.com/en-us/library/17w5ykft.aspx
  540. ("Parsing C++ Command-Line Arguments") to understand why we have to do
  541. this.
  542. Args:
  543. s: the string to be escaped.
  544. Returns:
  545. the escaped string.
  546. """
  547. def _Replace(match):
  548. # For a literal quote, CommandLineToArgv requires an odd number of
  549. # backslashes preceding it, and it produces half as many literal backslashes
  550. # (rounded down). So we need to produce 2n+1 backslashes.
  551. return 2 * match.group(1) + '\\"'
  552. # Escape all quotes so that they are interpreted literally.
  553. s = quote_replacer_regex.sub(_Replace, s)
  554. # Now add unescaped quotes so that any whitespace is interpreted literally.
  555. s = '"' + s + '"'
  556. return s
  557. delimiters_replacer_regex = re.compile(r'(\\*)([,;]+)')
  558. def _EscapeVCProjCommandLineArgListItem(s):
  559. """Escapes command line arguments for MSVS.
  560. The VCProj format stores string lists in a single string using commas and
  561. semi-colons as separators, which must be quoted if they are to be
  562. interpreted literally. However, command-line arguments may already have
  563. quotes, and the VCProj parser is ignorant of the backslash escaping
  564. convention used by CommandLineToArgv, so the command-line quotes and the
  565. VCProj quotes may not be the same quotes. So to store a general
  566. command-line argument in a VCProj list, we need to parse the existing
  567. quoting according to VCProj's convention and quote any delimiters that are
  568. not already quoted by that convention. The quotes that we add will also be
  569. seen by CommandLineToArgv, so if backslashes precede them then we also have
  570. to escape those backslashes according to the CommandLineToArgv
  571. convention.
  572. Args:
  573. s: the string to be escaped.
  574. Returns:
  575. the escaped string.
  576. """
  577. def _Replace(match):
  578. # For a non-literal quote, CommandLineToArgv requires an even number of
  579. # backslashes preceding it, and it produces half as many literal
  580. # backslashes. So we need to produce 2n backslashes.
  581. return 2 * match.group(1) + '"' + match.group(2) + '"'
  582. segments = s.split('"')
  583. # The unquoted segments are at the even-numbered indices.
  584. for i in range(0, len(segments), 2):
  585. segments[i] = delimiters_replacer_regex.sub(_Replace, segments[i])
  586. # Concatenate back into a single string
  587. s = '"'.join(segments)
  588. if len(segments) % 2 == 0:
  589. # String ends while still quoted according to VCProj's convention. This
  590. # means the delimiter and the next list item that follow this one in the
  591. # .vcproj file will be misinterpreted as part of this item. There is nothing
  592. # we can do about this. Adding an extra quote would correct the problem in
  593. # the VCProj but cause the same problem on the final command-line. Moving
  594. # the item to the end of the list does works, but that's only possible if
  595. # there's only one such item. Let's just warn the user.
  596. print >> sys.stderr, ('Warning: MSVS may misinterpret the odd number of ' +
  597. 'quotes in ' + s)
  598. return s
  599. def _EscapeCppDefineForMSVS(s):
  600. """Escapes a CPP define so that it will reach the compiler unaltered."""
  601. s = _EscapeEnvironmentVariableExpansion(s)
  602. s = _EscapeCommandLineArgumentForMSVS(s)
  603. s = _EscapeVCProjCommandLineArgListItem(s)
  604. return s
  605. quote_replacer_regex2 = re.compile(r'(\\+)"')
  606. def _EscapeCommandLineArgumentForMSBuild(s):
  607. """Escapes a Windows command-line argument for use by MSBuild."""
  608. def _Replace(match):
  609. return (len(match.group(1))/2*4)*'\\' + '\\"'
  610. # Escape all quotes so that they are interpreted literally.
  611. s = quote_replacer_regex2.sub(_Replace, s)
  612. return s
  613. def _EscapeMSBuildSpecialCharacters(s):
  614. escape_dictionary = {
  615. '%': '%25',
  616. '$': '%24',
  617. '@': '%40',
  618. "'": '%27',
  619. ';': '%3B',
  620. '?': '%3F',
  621. '*': '%2A'
  622. }
  623. result = ''.join([escape_dictionary.get(c, c) for c in s])
  624. return result
  625. def _EscapeCppDefineForMSBuild(s):
  626. """Escapes a CPP define so that it will reach the compiler unaltered."""
  627. s = _EscapeEnvironmentVariableExpansion(s)
  628. s = _EscapeCommandLineArgumentForMSBuild(s)
  629. s = _EscapeMSBuildSpecialCharacters(s)
  630. return s
  631. def _GenerateRulesForMSVS(p, output_dir, options, spec,
  632. sources, excluded_sources,
  633. actions_to_add):
  634. """Generate all the rules for a particular project.
  635. Arguments:
  636. p: the project
  637. output_dir: directory to emit rules to
  638. options: global options passed to the generator
  639. spec: the specification for this project
  640. sources: the set of all known source files in this project
  641. excluded_sources: the set of sources excluded from normal processing
  642. actions_to_add: deferred list of actions to add in
  643. """
  644. rules = spec.get('rules', [])
  645. rules_native = [r for r in rules if not int(r.get('msvs_external_rule', 0))]
  646. rules_external = [r for r in rules if int(r.get('msvs_external_rule', 0))]
  647. # Handle rules that use a native rules file.
  648. if rules_native:
  649. _GenerateNativeRulesForMSVS(p, rules_native, output_dir, spec, options)
  650. # Handle external rules (non-native rules).
  651. if rules_external:
  652. _GenerateExternalRules(rules_external, output_dir, spec,
  653. sources, options, actions_to_add)
  654. _AdjustSourcesForRules(rules, sources, excluded_sources)
  655. def _AdjustSourcesForRules(rules, sources, excluded_sources):
  656. # Add outputs generated by each rule (if applicable).
  657. for rule in rules:
  658. # Done if not processing outputs as sources.
  659. if int(rule.get('process_outputs_as_sources', False)):
  660. # Add in the outputs from this rule.
  661. trigger_files = _FindRuleTriggerFiles(rule, sources)
  662. for trigger_file in trigger_files:
  663. inputs, outputs = _RuleInputsAndOutputs(rule, trigger_file)
  664. inputs = set(_FixPaths(inputs))
  665. outputs = set(_FixPaths(outputs))
  666. inputs.remove(_FixPath(trigger_file))
  667. sources.update(inputs)
  668. excluded_sources.update(inputs)
  669. sources.update(outputs)
  670. def _FilterActionsFromExcluded(excluded_sources, actions_to_add):
  671. """Take inputs with actions attached out of the list of exclusions.
  672. Arguments:
  673. excluded_sources: list of source files not to be built.
  674. actions_to_add: dict of actions keyed on source file they're attached to.
  675. Returns:
  676. excluded_sources with files that have actions attached removed.
  677. """
  678. must_keep = set(_FixPaths(actions_to_add.keys()))
  679. return [s for s in excluded_sources if s not in must_keep]
  680. def _GetDefaultConfiguration(spec):
  681. return spec['configurations'][spec['default_configuration']]
  682. def _GetGuidOfProject(proj_path, spec):
  683. """Get the guid for the project.
  684. Arguments:
  685. proj_path: Path of the vcproj or vcxproj file to generate.
  686. spec: The target dictionary containing the properties of the target.
  687. Returns:
  688. the guid.
  689. Raises:
  690. ValueError: if the specified GUID is invalid.
  691. """
  692. # Pluck out the default configuration.
  693. default_config = _GetDefaultConfiguration(spec)
  694. # Decide the guid of the project.
  695. guid = default_config.get('msvs_guid')
  696. if guid:
  697. if VALID_MSVS_GUID_CHARS.match(guid) is None:
  698. raise ValueError('Invalid MSVS guid: "%s". Must match regex: "%s".' %
  699. (guid, VALID_MSVS_GUID_CHARS.pattern))
  700. guid = '{%s}' % guid
  701. guid = guid or MSVSNew.MakeGuid(proj_path)
  702. return guid
  703. def _GenerateProject(project, options, version, generator_flags):
  704. """Generates a vcproj file.
  705. Arguments:
  706. project: the MSVSProject object.
  707. options: global generator options.
  708. version: the MSVSVersion object.
  709. generator_flags: dict of generator-specific flags.
  710. """
  711. default_config = _GetDefaultConfiguration(project.spec)
  712. # Skip emitting anything if told to with msvs_existing_vcproj option.
  713. if default_config.get('msvs_existing_vcproj'):
  714. return
  715. if version.UsesVcxproj():
  716. _GenerateMSBuildProject(project, options, version, generator_flags)
  717. else:
  718. _GenerateMSVSProject(project, options, version, generator_flags)
  719. def _GenerateMSVSProject(project, options, version, generator_flags):
  720. """Generates a .vcproj file. It may create .rules and .user files too.
  721. Arguments:
  722. project: The project object we will generate the file for.
  723. options: Global options passed to the generator.
  724. version: The VisualStudioVersion object.
  725. generator_flags: dict of generator-specific flags.
  726. """
  727. spec = project.spec
  728. vcproj_dir = os.path.dirname(project.path)
  729. if vcproj_dir and not os.path.exists(vcproj_dir):
  730. os.makedirs(vcproj_dir)
  731. platforms = _GetUniquePlatforms(spec)
  732. p = MSVSProject.Writer(project.path, version, spec['target_name'],
  733. project.guid, platforms)
  734. # Get directory project file is in.
  735. project_dir = os.path.split(project.path)[0]
  736. gyp_path = _NormalizedSource(project.build_file)
  737. relative_path_of_gyp_file = gyp.common.RelativePath(gyp_path, project_dir)
  738. config_type = _GetMSVSConfigurationType(spec, project.build_file)
  739. for config_name, config in spec['configurations'].iteritems():
  740. _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config)
  741. # Prepare list of sources and excluded sources.
  742. gyp_file = os.path.split(project.build_file)[1]
  743. sources, excluded_sources = _PrepareListOfSources(spec, gyp_file)
  744. # Add rules.
  745. actions_to_add = {}
  746. _GenerateRulesForMSVS(p, project_dir, options, spec,
  747. sources, excluded_sources,
  748. actions_to_add)
  749. list_excluded = generator_flags.get('msvs_list_excluded_files', True)
  750. sources, excluded_sources, excluded_idl = (
  751. _AdjustSourcesAndConvertToFilterHierarchy(
  752. spec, options, project_dir, sources, excluded_sources, list_excluded))
  753. # Add in files.
  754. _VerifySourcesExist(sources, project_dir)
  755. p.AddFiles(sources)
  756. _AddToolFilesToMSVS(p, spec)
  757. _HandlePreCompiledHeaders(p, sources, spec)
  758. _AddActions(actions_to_add, spec, relative_path_of_gyp_file)
  759. _AddCopies(actions_to_add, spec)
  760. _WriteMSVSUserFile(project.path, version, spec)
  761. # NOTE: this stanza must appear after all actions have been decided.
  762. # Don't excluded sources with actions attached, or they won't run.
  763. excluded_sources = _FilterActionsFromExcluded(
  764. excluded_sources, actions_to_add)
  765. _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
  766. list_excluded)
  767. _AddAccumulatedActionsToMSVS(p, spec, actions_to_add)
  768. # Write it out.
  769. p.WriteIfChanged()
  770. def _GetUniquePlatforms(spec):
  771. """Returns the list of unique platforms for this spec, e.g ['win32', ...].
  772. Arguments:
  773. spec: The target dictionary containing the properties of the target.
  774. Returns:
  775. The MSVSUserFile object created.
  776. """
  777. # Gather list of unique platforms.
  778. platforms = set()
  779. for configuration in spec['configurations']:
  780. platforms.add(_ConfigPlatform(spec['configurations'][configuration]))
  781. platforms = list(platforms)
  782. return platforms
  783. def _CreateMSVSUserFile(proj_path, version, spec):
  784. """Generates a .user file for the user running this Gyp program.
  785. Arguments:
  786. proj_path: The path of the project file being created. The .user file
  787. shares the same path (with an appropriate suffix).
  788. version: The VisualStudioVersion object.
  789. spec: The target dictionary containing the properties of the target.
  790. Returns:
  791. The MSVSUserFile object created.
  792. """
  793. (domain, username) = _GetDomainAndUserName()
  794. vcuser_filename = '.'.join([proj_path, domain, username, 'user'])
  795. user_file = MSVSUserFile.Writer(vcuser_filename, version,
  796. spec['target_name'])
  797. return user_file
  798. def _GetMSVSConfigurationType(spec, build_file):
  799. """Returns the configuration type for this project.
  800. It's a number defined by Microsoft. May raise an exception.
  801. Args:
  802. spec: The target dictionary containing the properties of the target.
  803. build_file: The path of the gyp file.
  804. Returns:
  805. An integer, the configuration type.
  806. """
  807. try:
  808. config_type = {
  809. 'executable': '1', # .exe
  810. 'shared_library': '2', # .dll
  811. 'loadable_module': '2', # .dll
  812. 'static_library': '4', # .lib
  813. 'none': '10', # Utility type
  814. }[spec['type']]
  815. except KeyError:
  816. if spec.get('type'):
  817. raise Exception('Target type %s is not a valid target type for '
  818. 'target %s in %s.' %
  819. (spec['type'], spec['target_name'], build_file))
  820. else:
  821. raise Exception('Missing type field for target %s in %s.' %
  822. (spec['target_name'], build_file))
  823. return config_type
  824. def _AddConfigurationToMSVSProject(p, spec, config_type, config_name, config):
  825. """Adds a configuration to the MSVS project.
  826. Many settings in a vcproj file are specific to a configuration. This
  827. function the main part of the vcproj file that's configuration specific.
  828. Arguments:
  829. p: The target project being generated.
  830. spec: The target dictionary containing the properties of the target.
  831. config_type: The configuration type, a number as defined by Microsoft.
  832. config_name: The name of the configuration.
  833. config: The dictionnary that defines the special processing to be done
  834. for this configuration.
  835. """
  836. # Get the information for this configuration
  837. include_dirs, resource_include_dirs = _GetIncludeDirs(config)
  838. libraries = _GetLibraries(spec)
  839. out_file, vc_tool, _ = _GetOutputFilePathAndTool(spec)
  840. defines = _GetDefines(config)
  841. defines = [_EscapeCppDefineForMSVS(d) for d in defines]
  842. disabled_warnings = _GetDisabledWarnings(config)
  843. prebuild = config.get('msvs_prebuild')
  844. postbuild = config.get('msvs_postbuild')
  845. def_file = _GetModuleDefinition(spec)
  846. precompiled_header = config.get('msvs_precompiled_header')
  847. # Prepare the list of tools as a dictionary.
  848. tools = dict()
  849. # Add in user specified msvs_settings.
  850. msvs_settings = config.get('msvs_settings', {})
  851. MSVSSettings.ValidateMSVSSettings(msvs_settings)
  852. for tool in msvs_settings:
  853. settings = config['msvs_settings'][tool]
  854. for setting in settings:
  855. _ToolAppend(tools, tool, setting, settings[setting])
  856. # Add the information to the appropriate tool
  857. _ToolAppend(tools, 'VCCLCompilerTool',
  858. 'AdditionalIncludeDirectories', include_dirs)
  859. _ToolAppend(tools, 'VCResourceCompilerTool',
  860. 'AdditionalIncludeDirectories', resource_include_dirs)
  861. # Add in libraries.
  862. _ToolAppend(tools, 'VCLinkerTool', 'AdditionalDependencies', libraries)
  863. if out_file:
  864. _ToolAppend(tools, vc_tool, 'OutputFile', out_file, only_if_unset=True)
  865. # Add defines.
  866. _ToolAppend(tools, 'VCCLCompilerTool', 'PreprocessorDefinitions', defines)
  867. _ToolAppend(tools, 'VCResourceCompilerTool', 'PreprocessorDefinitions',
  868. defines)
  869. # Change program database directory to prevent collisions.
  870. _ToolAppend(tools, 'VCCLCompilerTool', 'ProgramDataBaseFileName',
  871. '$(IntDir)\\$(ProjectName)\\vc80.pdb', only_if_unset=True)
  872. # Add disabled warnings.
  873. _ToolAppend(tools, 'VCCLCompilerTool',
  874. 'DisableSpecificWarnings', disabled_warnings)
  875. # Add Pre-build.
  876. _ToolAppend(tools, 'VCPreBuildEventTool', 'CommandLine', prebuild)
  877. # Add Post-build.
  878. _ToolAppend(tools, 'VCPostBuildEventTool', 'CommandLine', postbuild)
  879. # Turn on precompiled headers if appropriate.
  880. if precompiled_header:
  881. precompiled_header = os.path.split(precompiled_header)[1]
  882. _ToolAppend(tools, 'VCCLCompilerTool', 'UsePrecompiledHeader', '2')
  883. _ToolAppend(tools, 'VCCLCompilerTool',
  884. 'PrecompiledHeaderThrough', precompiled_header)
  885. _ToolAppend(tools, 'VCCLCompilerTool',
  886. 'ForcedIncludeFiles', precompiled_header)
  887. # Loadable modules don't generate import libraries;
  888. # tell dependent projects to not expect one.
  889. if spec['type'] == 'loadable_module':
  890. _ToolAppend(tools, 'VCLinkerTool', 'IgnoreImportLibrary', 'true')
  891. # Set the module definition file if any.
  892. if def_file:
  893. _ToolAppend(tools, 'VCLinkerTool', 'ModuleDefinitionFile', def_file)
  894. _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name)
  895. def _GetIncludeDirs(config):
  896. """Returns the list of directories to be used for #include directives.
  897. Arguments:
  898. config: The dictionnary that defines the special processing to be done
  899. for this configuration.
  900. Returns:
  901. The list of directory paths.
  902. """
  903. # TODO(bradnelson): include_dirs should really be flexible enough not to
  904. # require this sort of thing.
  905. include_dirs = (
  906. config.get('include_dirs', []) +
  907. config.get('msvs_system_include_dirs', []))
  908. resource_include_dirs = config.get('resource_include_dirs', include_dirs)
  909. include_dirs = _FixPaths(include_dirs)
  910. resource_include_dirs = _FixPaths(resource_include_dirs)
  911. return include_dirs, resource_include_dirs
  912. def _GetLibraries(spec):
  913. """Returns the list of libraries for this configuration.
  914. Arguments:
  915. spec: The target dictionary containing the properties of the target.
  916. Returns:
  917. The list of directory paths.
  918. """
  919. libraries = spec.get('libraries', [])
  920. # Strip out -l, as it is not used on windows (but is needed so we can pass
  921. # in libraries that are assumed to be in the default library path).
  922. # Also remove duplicate entries, leaving only the last duplicate, while
  923. # preserving order.
  924. found = set()
  925. unique_libraries_list = []
  926. for entry in reversed(libraries):
  927. library = re.sub('^\-l', '', entry)
  928. if library not in found:
  929. found.add(library)
  930. unique_libraries_list.append(library)
  931. unique_libraries_list.reverse()
  932. return unique_libraries_list
  933. def _GetOutputFilePathAndTool(spec):
  934. """Returns the path and tool to use for this target.
  935. Figures out the path of the file this spec will create and the name of
  936. the VC tool that will create it.
  937. Arguments:
  938. spec: The target dictionary containing the properties of the target.
  939. Returns:
  940. A triple of (file path, name of the vc tool, name of the msbuild tool)
  941. """
  942. # Select a name for the output file.
  943. out_file = ''
  944. vc_tool = ''
  945. msbuild_tool = ''
  946. output_file_map = {
  947. 'executable': ('VCLinkerTool', 'Link', '$(OutDir)\\', '.exe'),
  948. 'shared_library': ('VCLinkerTool', 'Link', '$(OutDir)\\', '.dll'),
  949. 'loadable_module': ('VCLinkerTool', 'Link', '$(OutDir)\\', '.dll'),
  950. # TODO(jeanluc) If we want to avoid the MSB8012 warnings in
  951. # VisualStudio 2010, we will have to change the value of $(OutDir)
  952. # to contain the \lib suffix, rather than doing it as below.
  953. 'static_library': ('VCLibrarianTool', 'Lib', '$(OutDir)\\lib\\', '.lib'),
  954. }
  955. output_file_props = output_file_map.get(spec['type'])
  956. if output_file_props and int(spec.get('msvs_auto_output_file', 1)):
  957. vc_tool, msbuild_tool, out_dir, suffix = output_file_props
  958. out_dir = spec.get('product_dir', out_dir)
  959. product_extension = spec.get('product_extension')
  960. if product_extension:
  961. suffix = '.' + product_extension
  962. prefix = spec.get('product_prefix', '')
  963. product_name = spec.get('product_name', '$(ProjectName)')
  964. out_file = ntpath.join(out_dir, prefix + product_name + suffix)
  965. return out_file, vc_tool, msbuild_tool
  966. def _GetDefines(config):
  967. """Returns the list of preprocessor definitions for this configuation.
  968. Arguments:
  969. config: The dictionnary that defines the special processing to be done
  970. for this configuration.
  971. Returns:
  972. The list of preprocessor definitions.
  973. """
  974. defines = []
  975. for d in config.get('defines', []):
  976. if type(d) == list:
  977. fd = '='.join([str(dpart) for dpart in d])
  978. else:
  979. fd = str(d)
  980. defines.append(fd)
  981. return defines
  982. def _GetDisabledWarnings(config):
  983. return [str(i) for i in config.get('msvs_disabled_warnings', [])]
  984. def _GetModuleDefinition(spec):
  985. def_file = ''
  986. if spec['type'] in ['shared_library', 'loadable_module']:
  987. def_files = [s for s in spec.get('sources', []) if s.endswith('.def')]
  988. if len(def_files) == 1:
  989. def_file = _FixPath(def_files[0])
  990. elif def_files:
  991. raise ValueError(
  992. 'Multiple module definition files in one target, target %s lists '
  993. 'multiple .def files: %s' % (
  994. spec['target_name'], ' '.join(def_files)))
  995. return def_file
  996. def _ConvertToolsToExpectedForm(tools):
  997. """Convert tools to a form expected by Visual Studio.
  998. Arguments:
  999. tools: A dictionnary of settings; the tool name is the key.
  1000. Returns:
  1001. A list of Tool objects.
  1002. """
  1003. tool_list = []
  1004. for tool, settings in tools.iteritems():
  1005. # Collapse settings with lists.
  1006. settings_fixed = {}
  1007. for setting, value in settings.iteritems():
  1008. if type(value) == list:
  1009. if ((tool == 'VCLinkerTool' and
  1010. setting == 'AdditionalDependencies') or
  1011. setting == 'AdditionalOptions'):
  1012. settings_fixed[setting] = ' '.join(value)
  1013. else:
  1014. settings_fixed[setting] = ';'.join(value)
  1015. else:
  1016. settings_fixed[setting] = value
  1017. # Add in this tool.
  1018. tool_list.append(MSVSProject.Tool(tool, settings_fixed))
  1019. return tool_list
  1020. def _AddConfigurationToMSVS(p, spec, tools, config, config_type, config_name):
  1021. """Add to the project file the configuration specified by config.
  1022. Arguments:
  1023. p: The target project being generated.
  1024. spec: the target project dict.
  1025. tools: A dictionnary of settings; the tool name is the key.
  1026. config: The dictionnary that defines the special processing to be done
  1027. for this configuration.
  1028. config_type: The configuration type, a number as defined by Microsoft.
  1029. config_name: The name of the configuration.
  1030. """
  1031. attributes = _GetMSVSAttributes(spec, config, config_type)
  1032. # Add in this configuration.
  1033. tool_list = _ConvertToolsToExpectedForm(tools)
  1034. p.AddConfig(_ConfigFullName(config_name, config),
  1035. attrs=attributes, tools=tool_list)
  1036. def _GetMSVSAttributes(spec, config, config_type):
  1037. # Prepare configuration attributes.
  1038. prepared_attrs = {}
  1039. source_attrs = config.get('msvs_configuration_attributes', {})
  1040. for a in source_attrs:
  1041. prepared_attrs[a] = source_attrs[a]
  1042. # Add props files.
  1043. vsprops_dirs = config.get('msvs_props', [])
  1044. vsprops_dirs = _FixPaths(vsprops_dirs)
  1045. if vsprops_dirs:
  1046. prepared_attrs['InheritedPropertySheets'] = ';'.join(vsprops_dirs)
  1047. # Set configuration type.
  1048. prepared_attrs['ConfigurationType'] = config_type
  1049. output_dir = prepared_attrs.get('OutputDirectory',
  1050. '$(SolutionDir)$(ConfigurationName)')
  1051. # TODO(jeanluc) If we want to avoid the MSB8012 warning, we should
  1052. # add code like the following to place libraries in their own directory.
  1053. # if config_type == '4':
  1054. # output_dir = spec.get('product_dir', output_dir + '\\lib')
  1055. prepared_attrs['OutputDirectory'] = output_dir
  1056. if 'IntermediateDirectory' not in prepared_attrs:
  1057. intermediate = '$(ConfigurationName)\\obj\\$(ProjectName)'
  1058. prepared_attrs['IntermediateDirectory'] = intermediate
  1059. return prepared_attrs
  1060. def _AddNormalizedSources(sources_set, sources_array):
  1061. sources = [_NormalizedSource(s) for s in sources_array]
  1062. sources_set.update(set(sources))
  1063. def _PrepareListOfSources(spec, gyp_file):
  1064. """Prepare list of sources and excluded sources.
  1065. Besides the sources specified directly in the spec, adds the gyp file so
  1066. that a change to it will cause a re-compile. Also adds appropriate sources
  1067. for actions and copies. Assumes later stage will un-exclude files which
  1068. have custom build steps attached.
  1069. Arguments:
  1070. spec: The target dictionary containing the properties of the target.
  1071. gyp_file: The name of the gyp file.
  1072. Returns:
  1073. A pair of (list of sources, list of excluded sources).
  1074. The sources will be relative to the gyp file.
  1075. """
  1076. sources = set()
  1077. _AddNormalizedSources(sources, spec.get('sources', []))
  1078. excluded_sources = set()
  1079. # Add in the gyp file.
  1080. sources.add(gyp_file)
  1081. # Add in 'action' inputs and outputs.
  1082. for a in spec.get('actions', []):
  1083. inputs = a.get('inputs', [])
  1084. inputs = [_NormalizedSource(i) for i in inputs]
  1085. # Add all inputs to sources and excluded sources.
  1086. inputs = set(inputs)
  1087. sources.update(inputs)
  1088. excluded_sources.update(inputs)
  1089. if int(a.get('process_outputs_as_sources', False)):
  1090. _AddNormalizedSources(sources, a.get('outputs', []))
  1091. # Add in 'copies' inputs and outputs.
  1092. for cpy in spec.get('copies', []):
  1093. _AddNormalizedSources(sources, cpy.get('files', []))
  1094. return (sources, excluded_sources)
  1095. def _AdjustSourcesAndConvertToFilterHierarchy(
  1096. spec, options, gyp_dir, sources, excluded_sources, list_excluded):
  1097. """Adjusts the list of sources and excluded sources.
  1098. Also converts the sets to lists.
  1099. Arguments:
  1100. spec: The target dictionary containing the properties of the target.
  1101. options: Global generator options.
  1102. gyp_dir: The path to the gyp file being processed.
  1103. sources: A set of sources to be included for this project.
  1104. excluded_sources: A set of sources to be excluded for this project.
  1105. Returns:
  1106. A trio of (list of sources, list of excluded sources,
  1107. path of excluded IDL file)
  1108. """
  1109. # Exclude excluded sources coming into the generator.
  1110. excluded_sources.update(set(spec.get('sources_excluded', [])))
  1111. # Add excluded sources into sources for good measure.
  1112. sources.update(excluded_sources)
  1113. # Convert to proper windows form.
  1114. # NOTE: sources goes from being a set to a list here.
  1115. # NOTE: excluded_sources goes from being a set to a list here.
  1116. sources = _FixPaths(sources)
  1117. # Convert to proper windows form.
  1118. excluded_sources = _FixPaths(excluded_sources)
  1119. excluded_idl = _IdlFilesHandledNonNatively(spec, sources)
  1120. precompiled_related = _GetPrecompileRelatedFiles(spec)
  1121. # Find the excluded ones, minus the precompiled header related ones.
  1122. fully_excluded = [i for i in excluded_sources if i not in precompiled_related]
  1123. # Convert to folders and the right slashes.
  1124. sources = [i.split('\\') for i in sources]
  1125. sources = _ConvertSourcesToFilterHierarchy(sources, excluded=fully_excluded,
  1126. list_excluded=list_excluded)
  1127. return sources, excluded_sources, excluded_idl
  1128. def _IdlFilesHandledNonNatively(spec, sources):
  1129. # If any non-native rules use 'idl' as an extension exclude idl files.
  1130. # Gather a list here to use later.
  1131. using_idl = False
  1132. for rule in spec.get('rules', []):
  1133. if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)):
  1134. using_idl = True
  1135. break
  1136. if using_idl:
  1137. excluded_idl = [i for i in sources if i.endswith('.idl')]
  1138. else:
  1139. excluded_idl = []
  1140. return excluded_idl
  1141. def _GetPrecompileRelatedFiles(spec):
  1142. # Gather a list of precompiled header related sources.
  1143. precompiled_related = []
  1144. for _, config in spec['configurations'].iteritems():
  1145. for k in precomp_keys:
  1146. f = config.get(k)
  1147. if f:
  1148. precompiled_related.append(_FixPath(f))
  1149. return precompiled_related
  1150. def _ExcludeFilesFromBeingBuilt(p, spec, excluded_sources, excluded_idl,
  1151. list_excluded):
  1152. exclusions = _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl)
  1153. for file_name, excluded_configs in exclusions.iteritems():
  1154. if (not list_excluded and
  1155. len(excluded_configs) == len(spec['configurations'])):
  1156. # If we're not listing excluded files, then they won't appear in the
  1157. # project, so don't try to configure them to be excluded.
  1158. pass
  1159. else:
  1160. for config_name, config in excluded_configs:
  1161. p.AddFileConfig(file_name, _ConfigFullName(config_name, config),
  1162. {'ExcludedFromBuild': 'true'})
  1163. def _GetExcludedFilesFromBuild(spec, excluded_sources, excluded_idl):
  1164. exclusions = {}
  1165. # Exclude excluded sources from being built.
  1166. for f in excluded_sources:
  1167. excluded_configs = []
  1168. for config_name, config in spec['configurations'].iteritems():
  1169. precomped = [_FixPath(config.get(i, '')) for i in precomp_keys]
  1170. # Don't do this for ones that are precompiled header related.
  1171. if f n

Large files files are truncated, but you can click here to view the full file