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