From eaeb0a8dd405c52fd0612a1e96cb2cbf7cab5ede Mon Sep 17 00:00:00 2001 From: Cheng Zhao Date: Wed, 11 Aug 2021 10:40:33 +0900 Subject: [PATCH] fix: support msvs_quote_cmd in ninja generator --- pylib/gyp/generator/ninja.py | 31 +++++++++++++------------ pylib/gyp/msvs_emulation.py | 45 +++++++++++++++++++++++++----------- 2 files changed, 47 insertions(+), 29 deletions(-) diff --git a/pylib/gyp/generator/ninja.py b/pylib/gyp/generator/ninja.py index cfe3c49a..e5d0316d 100644 --- a/pylib/gyp/generator/ninja.py +++ b/pylib/gyp/generator/ninja.py @@ -654,10 +654,10 @@ def WriteActions( description = self.GenerateDescription( "ACTION", action.get("message", None), name ) - is_cygwin = ( - self.msvs_settings.IsRuleRunUnderCygwin(action) - if self.flavor == "win" - else False + win_shell_flags = ( + self.msvs_settings.GetRuleShellFlags(action) + if self.flavor == 'win' + else None ) args = action["action"] depfile = action.get("depfile", None) @@ -665,7 +665,7 @@ def WriteActions( depfile = self.ExpandSpecial(depfile, self.base_to_build) pool = "console" if int(action.get("ninja_use_console", 0)) else None rule_name, _ = self.WriteNewNinjaRule( - name, args, description, is_cygwin, env, pool, depfile=depfile + name, args, description, win_shell_flags, env, pool, depfile=depfile ) inputs = [self.GypPathToNinja(i, env) for i in action["inputs"]] @@ -707,14 +707,14 @@ def WriteRules( rule.get("message", None), ("%s " + generator_default_variables["RULE_INPUT_PATH"]) % name, ) - is_cygwin = ( - self.msvs_settings.IsRuleRunUnderCygwin(rule) - if self.flavor == "win" - else False + win_shell_flags = ( + self.msvs_settings.GetRuleShellFlags(action) + if self.flavor == 'win' + else None ) pool = "console" if int(rule.get("ninja_use_console", 0)) else None rule_name, args = self.WriteNewNinjaRule( - name, args, description, is_cygwin, env, pool + name, args, description, win_shell_flags, env, pool ) # TODO: if the command references the outputs directly, we should @@ -733,7 +733,7 @@ def WriteRules( def cygwin_munge(path): # pylint: disable=cell-var-from-loop - if is_cygwin: + if win_shell_flags and win_shell_flags.cygwin: return path.replace("\\", "/") return path @@ -1899,7 +1899,7 @@ def WriteVariableList(self, ninja_file, var, values): ninja_file.variable(var, " ".join(values)) def WriteNewNinjaRule( - self, name, args, description, is_cygwin, env, pool, depfile=None + self, name, args, description, win_shell_flags, env, pool, depfile=None ): """Write out a new ninja "rule" statement for a given command. @@ -1946,13 +1946,14 @@ def WriteNewNinjaRule( if self.flavor == "win": rspfile = rule_name + ".$unique_name.rsp" # The cygwin case handles this inside the bash sub-shell. - run_in = "" if is_cygwin else " " + self.build_to_base - if is_cygwin: + run_in = "" if win_shell_flags.cygwin else " " + self.build_to_base + if win_shell_flags.cygwin: rspfile_content = self.msvs_settings.BuildCygwinBashCommandLine( args, self.build_to_base ) else: - rspfile_content = gyp.msvs_emulation.EncodeRspFileList(args) + rspfile_content = gyp.msvs_emulation.EncodeRspFileList( + args, win_shell_flags.quote) command = ( "%s gyp-win-tool action-wrapper $arch " % sys.executable + rspfile diff --git a/pylib/gyp/msvs_emulation.py b/pylib/gyp/msvs_emulation.py index 6fcabd04..18c3031d 100644 --- a/pylib/gyp/msvs_emulation.py +++ b/pylib/gyp/msvs_emulation.py @@ -7,6 +7,7 @@ build systems, primarily ninja. """ +import collections import os import re import subprocess @@ -19,7 +20,7 @@ windows_quoter_regex = re.compile(r'(\\*)"') -def QuoteForRspFile(arg): +def QuoteForRspFile(arg, quote_cmd=True): """Quote a command line argument so that it appears as one argument when processed via cmd.exe and parsed by CommandLineToArgvW (as is typical for Windows programs).""" @@ -36,7 +37,8 @@ def QuoteForRspFile(arg): # For a literal quote, CommandLineToArgvW requires 2n+1 backslashes # preceding it, and results in n backslashes + the quote. So we substitute # in 2* what we match, +1 more, plus the quote. - arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg) + if quote_cmd: + arg = windows_quoter_regex.sub(lambda mo: 2 * mo.group(1) + '\\"', arg) # %'s also need to be doubled otherwise they're interpreted as batch # positional arguments. Also make sure to escape the % so that they're @@ -48,12 +50,17 @@ def QuoteForRspFile(arg): # These commands are used in rsp files, so no escaping for the shell (via ^) # is necessary. - # Finally, wrap the whole thing in quotes so that the above quote rule - # applies and whitespace isn't a word break. - return '"' + arg + '"' + # As a workaround for programs that don't use CommandLineToArgvW, gyp + # supports msvs_quote_cmd=0, which simply disables all quoting. + if quote_cmd: + # Finally, wrap the whole thing in quotes so that the above quote rule + # applies and whitespace isn't a word break. + return '"' + arg + '"' + return arg -def EncodeRspFileList(args): + +def EncodeRspFileList(args, quote_cmd): """Process a list of arguments using QuoteCmdExeArgument.""" # Note that the first argument is assumed to be the command. Don't add # quotes around it because then built-ins like 'echo', etc. won't work. @@ -67,7 +74,8 @@ def EncodeRspFileList(args): program = call + " " + os.path.normpath(program) else: program = os.path.normpath(args[0]) - return program + " " + " ".join(QuoteForRspFile(arg) for arg in args[1:]) + return (program + ' ' + + ' '.join(QuoteForRspFile(arg, quote_cmd) for arg in args[1:])) def _GenericRetrieve(root, default, path): @@ -933,13 +941,22 @@ def BuildCygwinBashCommandLine(self, args, path_to_base): ) return cmd - def IsRuleRunUnderCygwin(self, rule): - """Determine if an action should be run under cygwin. If the variable is - unset, or set to 1 we use cygwin.""" - return ( - int(rule.get("msvs_cygwin_shell", self.spec.get("msvs_cygwin_shell", 1))) - != 0 - ) + RuleShellFlags = collections.namedtuple('RuleShellFlags', ['cygwin', 'quote']) + + def GetRuleShellFlags(self, rule): + """Return RuleShellFlags about how the given rule should be run. This + includes whether it should run under cygwin (msvs_cygwin_shell), and + whether the commands should be quoted (msvs_quote_cmd).""" + # If the variable is unset, or set to 1 we use cygwin + cygwin = int(rule.get('msvs_cygwin_shell', + self.spec.get('msvs_cygwin_shell', 1))) != 0 + # Default to quoting. There's only a few special instances where the + # target command uses non-standard command line parsing and handle quotes + # and quote escaping differently. + quote_cmd = int(rule.get('msvs_quote_cmd', 1)) + assert not (quote_cmd == 0 and cygwin == 1), \ + 'msvs_quote_cmd=0 only applicable for msvs_cygwin_shell=0' + return MsvsSettings.RuleShellFlags(cygwin, quote_cmd) def _HasExplicitRuleForExtension(self, spec, extension): """Determine if there's an explicit rule for a particular extension."""