ninja: Use shlex.quote for quoting on non-Windows

This is more reliable, and more accurate. For instance, this means
arguments in commands aren't surrounded by `'` on Linux unless that
is actually needed by that specific argument.

There is no equivalent helper for Windows, so we keep the old
behaviour for that.
pull/1545/head
Nirbheek Chauhan 8 years ago
parent ae9b23832e
commit 4b428053f4
  1. 46
      mesonbuild/backend/ninjabackend.py
  2. 16
      run_unittests.py

@ -12,6 +12,11 @@
# See the License for the specific language governing permissions and # See the License for the specific language governing permissions and
# limitations under the License. # limitations under the License.
import shlex
import os, sys, pickle, re
import subprocess, shutil
from collections import OrderedDict
from . import backends from . import backends
from .. import modules from .. import modules
from .. import environment, mesonlib from .. import environment, mesonlib
@ -24,16 +29,13 @@ from ..mesonlib import File, MesonException, OrderedSet
from ..mesonlib import get_meson_script, get_compiler_for_source from ..mesonlib import get_meson_script, get_compiler_for_source
from .backends import CleanTrees, InstallData from .backends import CleanTrees, InstallData
from ..build import InvalidArguments from ..build import InvalidArguments
import os, sys, pickle, re
import subprocess, shutil
from collections import OrderedDict
if mesonlib.is_windows(): if mesonlib.is_windows():
quote_char = '"' quote_func = lambda s: '"{}"'.format(s)
execute_wrapper = 'cmd /c' execute_wrapper = 'cmd /c'
rmfile_prefix = 'del /f /s /q {} &&' rmfile_prefix = 'del /f /s /q {} &&'
else: else:
quote_char = "'" quote_func = shlex.quote
execute_wrapper = '' execute_wrapper = ''
rmfile_prefix = 'rm -f {} &&' rmfile_prefix = 'rm -f {} &&'
@ -105,18 +107,17 @@ class NinjaBuildElement:
(name, elems) = e (name, elems) = e
should_quote = name not in raw_names should_quote = name not in raw_names
line = ' %s = ' % name line = ' %s = ' % name
q_templ = quote_char + "%s" + quote_char
noq_templ = "%s" noq_templ = "%s"
newelems = [] newelems = []
for i in elems: for i in elems:
if not should_quote or i == '&&': # Hackety hack hack if not should_quote or i == '&&': # Hackety hack hack
templ = noq_templ quoter = ninja_quote
else: else:
templ = q_templ quoter = lambda x: ninja_quote(quote_func(x))
i = i.replace('\\', '\\\\') i = i.replace('\\', '\\\\')
if quote_char == '"': if quote_func('') == '""':
i = i.replace('"', '\\"') i = i.replace('"', '\\"')
newelems.append(templ % ninja_quote(i)) newelems.append(quoter(i))
line += ' '.join(newelems) line += ' '.join(newelems)
line += '\n' line += '\n'
outfile.write(line) outfile.write(line)
@ -854,12 +855,12 @@ int dummy;
outfile.write(' depfile = $DEPFILE\n') outfile.write(' depfile = $DEPFILE\n')
outfile.write(' restat = 1\n\n') outfile.write(' restat = 1\n\n')
outfile.write('rule REGENERATE_BUILD\n') outfile.write('rule REGENERATE_BUILD\n')
c = (quote_char + ninja_quote(sys.executable) + quote_char, c = (ninja_quote(quote_func(sys.executable)),
quote_char + ninja_quote(self.environment.get_build_command()) + quote_char, ninja_quote(quote_func(self.environment.get_build_command())),
'--internal', '--internal',
'regenerate', 'regenerate',
quote_char + ninja_quote(self.environment.get_source_dir()) + quote_char, ninja_quote(quote_func(self.environment.get_source_dir())),
quote_char + ninja_quote(self.environment.get_build_dir()) + quote_char) ninja_quote(quote_func(self.environment.get_build_dir())))
outfile.write(" command = %s %s %s %s %s %s --backend ninja\n" % c) outfile.write(" command = %s %s %s %s %s %s --backend ninja\n" % c)
outfile.write(' description = Regenerating build files.\n') outfile.write(' description = Regenerating build files.\n')
outfile.write(' generator = 1\n\n') outfile.write(' generator = 1\n\n')
@ -1515,7 +1516,7 @@ rule FORTRAN_DEP_HACK
pass pass
return [] return []
def generate_compile_rule_for(self, langname, compiler, qstr, is_cross, outfile): def generate_compile_rule_for(self, langname, compiler, is_cross, outfile):
if langname == 'java': if langname == 'java':
if not is_cross: if not is_cross:
self.generate_java_compile_rule(compiler, outfile) self.generate_java_compile_rule(compiler, outfile)
@ -1547,7 +1548,7 @@ rule FORTRAN_DEP_HACK
quoted_depargs = [] quoted_depargs = []
for d in depargs: for d in depargs:
if d != '$out' and d != '$in': if d != '$out' and d != '$in':
d = qstr % d d = quote_func(d)
quoted_depargs.append(d) quoted_depargs.append(d)
cross_args = self.get_cross_info_lang_args(langname, is_cross) cross_args = self.get_cross_info_lang_args(langname, is_cross)
if mesonlib.is_windows(): if mesonlib.is_windows():
@ -1576,7 +1577,7 @@ rule FORTRAN_DEP_HACK
outfile.write(description) outfile.write(description)
outfile.write('\n') outfile.write('\n')
def generate_pch_rule_for(self, langname, compiler, qstr, is_cross, outfile): def generate_pch_rule_for(self, langname, compiler, is_cross, outfile):
if langname != 'c' and langname != 'cpp': if langname != 'c' and langname != 'cpp':
return return
if is_cross: if is_cross:
@ -1595,7 +1596,7 @@ rule FORTRAN_DEP_HACK
quoted_depargs = [] quoted_depargs = []
for d in depargs: for d in depargs:
if d != '$out' and d != '$in': if d != '$out' and d != '$in':
d = qstr % d d = quote_func(d)
quoted_depargs.append(d) quoted_depargs.append(d)
if compiler.get_id() == 'msvc': if compiler.get_id() == 'msvc':
output = '' output = ''
@ -1621,12 +1622,11 @@ rule FORTRAN_DEP_HACK
outfile.write('\n') outfile.write('\n')
def generate_compile_rules(self, outfile): def generate_compile_rules(self, outfile):
qstr = quote_char + "%s" + quote_char
for langname, compiler in self.build.compilers.items(): for langname, compiler in self.build.compilers.items():
if compiler.get_id() == 'clang': if compiler.get_id() == 'clang':
self.generate_llvm_ir_compile_rule(compiler, False, outfile) self.generate_llvm_ir_compile_rule(compiler, False, outfile)
self.generate_compile_rule_for(langname, compiler, qstr, False, outfile) self.generate_compile_rule_for(langname, compiler, False, outfile)
self.generate_pch_rule_for(langname, compiler, qstr, False, outfile) self.generate_pch_rule_for(langname, compiler, False, outfile)
if self.environment.is_cross_build(): if self.environment.is_cross_build():
# In case we are going a target-only build, make the native compilers # In case we are going a target-only build, make the native compilers
# masquerade as cross compilers. # masquerade as cross compilers.
@ -1637,8 +1637,8 @@ rule FORTRAN_DEP_HACK
for langname, compiler in cclist.items(): for langname, compiler in cclist.items():
if compiler.get_id() == 'clang': if compiler.get_id() == 'clang':
self.generate_llvm_ir_compile_rule(compiler, True, outfile) self.generate_llvm_ir_compile_rule(compiler, True, outfile)
self.generate_compile_rule_for(langname, compiler, qstr, True, outfile) self.generate_compile_rule_for(langname, compiler, True, outfile)
self.generate_pch_rule_for(langname, compiler, qstr, True, outfile) self.generate_pch_rule_for(langname, compiler, True, outfile)
outfile.write('\n') outfile.write('\n')
def generate_generator_list_rules(self, target, outfile): def generate_generator_list_rules(self, target, outfile):

@ -1266,15 +1266,15 @@ class LinuxlikeTests(BasePlatformTests):
self.assertIsNotNone(vala_command) self.assertIsNotNone(vala_command)
self.assertIsNotNone(c_command) self.assertIsNotNone(c_command)
# -w suppresses all warnings, should be there in Vala but not in C # -w suppresses all warnings, should be there in Vala but not in C
self.assertIn("'-w'", vala_command) self.assertIn(" -w ", vala_command)
self.assertNotIn("'-w'", c_command) self.assertNotIn(" -w ", c_command)
# -Wall enables all warnings, should be there in C but not in Vala # -Wall enables all warnings, should be there in C but not in Vala
self.assertNotIn("'-Wall'", vala_command) self.assertNotIn(" -Wall ", vala_command)
self.assertIn("'-Wall'", c_command) self.assertIn(" -Wall ", c_command)
# -Werror converts warnings to errors, should always be there since it's # -Werror converts warnings to errors, should always be there since it's
# injected by an unrelated piece of code and the project has werror=true # injected by an unrelated piece of code and the project has werror=true
self.assertIn("'-Werror'", vala_command) self.assertIn(" -Werror ", vala_command)
self.assertIn("'-Werror'", c_command) self.assertIn(" -Werror ", c_command)
def test_qt5dependency_pkgconfig_detection(self): def test_qt5dependency_pkgconfig_detection(self):
''' '''
@ -1405,7 +1405,7 @@ class LinuxlikeTests(BasePlatformTests):
self.init(testdir, ['-D' + std_opt]) self.init(testdir, ['-D' + std_opt])
cmd = self.get_compdb()[0]['command'] cmd = self.get_compdb()[0]['command']
if v != 'none': if v != 'none':
cmd_std = "'-std={}'".format(v) cmd_std = " -std={} ".format(v)
self.assertIn(cmd_std, cmd) self.assertIn(cmd_std, cmd)
try: try:
self.build() self.build()
@ -1420,7 +1420,7 @@ class LinuxlikeTests(BasePlatformTests):
os.environ[env_flags] = cmd_std os.environ[env_flags] = cmd_std
self.init(testdir) self.init(testdir)
cmd = self.get_compdb()[0]['command'] cmd = self.get_compdb()[0]['command']
qcmd_std = "'{}'".format(cmd_std) qcmd_std = " {} ".format(cmd_std)
self.assertIn(qcmd_std, cmd) self.assertIn(qcmd_std, cmd)
with self.assertRaises(subprocess.CalledProcessError, with self.assertRaises(subprocess.CalledProcessError,
msg='{} should have failed'.format(qcmd_std)): msg='{} should have failed'.format(qcmd_std)):

Loading…
Cancel
Save