OLD | NEW |
1 # Copyright (c) 2012 Google Inc. All rights reserved. | 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 | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 """ | 5 """ |
6 This module helps emulate Visual Studio 2008 behavior on top of other | 6 This module helps emulate Visual Studio 2008 behavior on top of other |
7 build systems, primarily ninja. | 7 build systems, primarily ninja. |
8 """ | 8 """ |
9 | 9 |
| 10 import collections |
10 import os | 11 import os |
11 import re | 12 import re |
12 import subprocess | 13 import subprocess |
13 import sys | 14 import sys |
14 | 15 |
15 import gyp.MSVSVersion | 16 import gyp.MSVSVersion |
16 | 17 |
17 windows_quoter_regex = re.compile(r'(\\*)"') | 18 windows_quoter_regex = re.compile(r'(\\*)"') |
18 | 19 |
19 def QuoteForRspFile(arg): | 20 def QuoteForRspFile(arg, quote_cmd=True): |
20 """Quote a command line argument so that it appears as one argument when | 21 """Quote a command line argument so that it appears as one argument when |
21 processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for | 22 processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for |
22 Windows programs).""" | 23 Windows programs).""" |
23 # See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment | 24 # See http://goo.gl/cuFbX and http://goo.gl/dhPnp including the comment |
24 # threads. This is actually the quoting rules for CommandLineToArgvW, not | 25 # threads. This is actually the quoting rules for CommandLineToArgvW, not |
25 # for the shell, because the shell doesn't do anything in Windows. This | 26 # for the shell, because the shell doesn't do anything in Windows. This |
26 # works more or less because most programs (including the compiler, etc.) | 27 # works more or less because most programs (including the compiler, etc.) |
27 # use that function to handle command line arguments. | 28 # use that function to handle command line arguments. |
28 | 29 |
29 # For a literal quote, CommandLineToArgvW requires 2n+1 backslashes | 30 # For a literal quote, CommandLineToArgvW requires 2n+1 backslashes |
30 # preceding it, and results in n backslashes + the quote. So we substitute | 31 # preceding it, and results in n backslashes + the quote. So we substitute |
31 # in 2* what we match, +1 more, plus the quote. | 32 # in 2* what we match, +1 more, plus the quote. |
32 arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg) | 33 if quote_cmd: |
| 34 arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg) |
33 | 35 |
34 # %'s also need to be doubled otherwise they're interpreted as batch | 36 # %'s also need to be doubled otherwise they're interpreted as batch |
35 # positional arguments. Also make sure to escape the % so that they're | 37 # positional arguments. Also make sure to escape the % so that they're |
36 # passed literally through escaping so they can be singled to just the | 38 # passed literally through escaping so they can be singled to just the |
37 # original %. Otherwise, trying to pass the literal representation that | 39 # original %. Otherwise, trying to pass the literal representation that |
38 # looks like an environment variable to the shell (e.g. %PATH%) would fail. | 40 # looks like an environment variable to the shell (e.g. %PATH%) would fail. |
39 arg = arg.replace('%', '%%') | 41 arg = arg.replace('%', '%%') |
40 | 42 |
41 # These commands are used in rsp files, so no escaping for the shell (via ^) | 43 # These commands are used in rsp files, so no escaping for the shell (via ^) |
42 # is necessary. | 44 # is necessary. |
43 | 45 |
44 # Finally, wrap the whole thing in quotes so that the above quote rule | 46 # As a workaround for programs that don't use CommandLineToArgvW, gyp |
45 # applies and whitespace isn't a word break. | 47 # supports msvs_quote_cmd=0, which simply disables all quoting. |
46 return '"' + arg + '"' | 48 if quote_cmd: |
| 49 # Finally, wrap the whole thing in quotes so that the above quote rule |
| 50 # applies and whitespace isn't a word break. |
| 51 arg = '"' + arg + '"' |
47 | 52 |
| 53 return arg |
48 | 54 |
49 def EncodeRspFileList(args): | 55 def EncodeRspFileList(args, quote_cmd): |
50 """Process a list of arguments using QuoteCmdExeArgument.""" | 56 """Process a list of arguments using QuoteCmdExeArgument.""" |
51 # Note that the first argument is assumed to be the command. Don't add | 57 # Note that the first argument is assumed to be the command. Don't add |
52 # quotes around it because then built-ins like 'echo', etc. won't work. | 58 # quotes around it because then built-ins like 'echo', etc. won't work. |
53 # Take care to normpath only the path in the case of 'call ../x.bat' because | 59 # Take care to normpath only the path in the case of 'call ../x.bat' because |
54 # otherwise the whole thing is incorrectly interpreted as a path and not | 60 # otherwise the whole thing is incorrectly interpreted as a path and not |
55 # normalized correctly. | 61 # normalized correctly. |
56 if not args: return '' | 62 if not args: return '' |
57 if args[0].startswith('call '): | 63 if args[0].startswith('call '): |
58 call, program = args[0].split(' ', 1) | 64 call, program = args[0].split(' ', 1) |
59 program = call + ' ' + os.path.normpath(program) | 65 program = call + ' ' + os.path.normpath(program) |
60 else: | 66 else: |
61 program = os.path.normpath(args[0]) | 67 program = os.path.normpath(args[0]) |
62 return program + ' ' + ' '.join(QuoteForRspFile(arg) for arg in args[1:]) | 68 return (program + ' ' + |
| 69 ' '.join(QuoteForRspFile(arg, quote_cmd) for arg in args[1:])) |
63 | 70 |
64 | 71 |
65 def _GenericRetrieve(root, default, path): | 72 def _GenericRetrieve(root, default, path): |
66 """Given a list of dictionary keys |path| and a tree of dicts |root|, find | 73 """Given a list of dictionary keys |path| and a tree of dicts |root|, find |
67 value at path, or return |default| if any of the path doesn't exist.""" | 74 value at path, or return |default| if any of the path doesn't exist.""" |
68 if not root: | 75 if not root: |
69 return default | 76 return default |
70 if not path: | 77 if not path: |
71 return root | 78 return root |
72 return _GenericRetrieve(root.get(path[0]), default, path[1:]) | 79 return _GenericRetrieve(root.get(path[0]), default, path[1:]) |
(...skipping 318 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
391 os.path.join(path_to_base, self.msvs_cygwin_dirs[0])) | 398 os.path.join(path_to_base, self.msvs_cygwin_dirs[0])) |
392 cd = ('cd %s' % path_to_base).replace('\\', '/') | 399 cd = ('cd %s' % path_to_base).replace('\\', '/') |
393 args = [a.replace('\\', '/') for a in args] | 400 args = [a.replace('\\', '/') for a in args] |
394 args = ["'%s'" % a.replace("'", "\\'") for a in args] | 401 args = ["'%s'" % a.replace("'", "\\'") for a in args] |
395 bash_cmd = ' '.join(args) | 402 bash_cmd = ' '.join(args) |
396 cmd = ( | 403 cmd = ( |
397 'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir + | 404 'call "%s\\setup_env.bat" && set CYGWIN=nontsec && ' % cygwin_dir + |
398 'bash -c "%s ; %s"' % (cd, bash_cmd)) | 405 'bash -c "%s ; %s"' % (cd, bash_cmd)) |
399 return cmd | 406 return cmd |
400 | 407 |
401 def IsRuleRunUnderCygwin(self, rule): | 408 RuleShellFlags = collections.namedtuple('RuleShellFlags', ['cygwin', 'quote']) |
402 """Determine if an action should be run under cygwin. If the variable is | 409 |
403 unset, or set to 1 we use cygwin.""" | 410 def GetRuleShellFlags(self, rule): |
404 return int(rule.get('msvs_cygwin_shell', | 411 """Return RuleShellFlags about how the given rule should be run. This |
405 self.spec.get('msvs_cygwin_shell', 1))) != 0 | 412 includes whether it should run under cygwin (msvs_cygwin_shell), and |
| 413 whether the commands should be quoted (msvs_quote_cmd).""" |
| 414 # If the variable is unset, or set to 1 we use cygwin |
| 415 cygwin = int(rule.get('msvs_cygwin_shell', |
| 416 self.spec.get('msvs_cygwin_shell', 1))) != 0 |
| 417 # Default to quoting. There's only a few special instances where the |
| 418 # target command uses non-standard command line parsing and handle quotes |
| 419 # and quote escaping differently. |
| 420 quote_cmd = int(rule.get('msvs_quote_cmd', 1)) |
| 421 assert not (quote_cmd == 0 and cygwin == 1), \ |
| 422 'msvs_quote_cmd=0 only applicable for msvs_cygwin_shell=0' |
| 423 return MsvsSettings.RuleShellFlags(cygwin, quote_cmd) |
406 | 424 |
407 def HasExplicitIdlRules(self, spec): | 425 def HasExplicitIdlRules(self, spec): |
408 """Determine if there's an explicit rule for idl files. When there isn't we | 426 """Determine if there's an explicit rule for idl files. When there isn't we |
409 need to generate implicit rules to build MIDL .idl files.""" | 427 need to generate implicit rules to build MIDL .idl files.""" |
410 for rule in spec.get('rules', []): | 428 for rule in spec.get('rules', []): |
411 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)): | 429 if rule['extension'] == 'idl' and int(rule.get('msvs_external_rule', 0)): |
412 return True | 430 return True |
413 return False | 431 return False |
414 | 432 |
415 def GetIdlBuildData(self, source, config): | 433 def GetIdlBuildData(self, source, config): |
(...skipping 52 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
468 return vs.SetupScript() | 486 return vs.SetupScript() |
469 | 487 |
470 def ExpandMacros(string, expansions): | 488 def ExpandMacros(string, expansions): |
471 """Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv | 489 """Expand $(Variable) per expansions dict. See MsvsSettings.GetVSMacroEnv |
472 for the canonical way to retrieve a suitable dict.""" | 490 for the canonical way to retrieve a suitable dict.""" |
473 if '$' in string: | 491 if '$' in string: |
474 for old, new in expansions.iteritems(): | 492 for old, new in expansions.iteritems(): |
475 assert '$(' not in new, new | 493 assert '$(' not in new, new |
476 string = string.replace(old, new) | 494 string = string.replace(old, new) |
477 return string | 495 return string |
OLD | NEW |