aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJon Turney <jon.turney@dronecode.org.uk>2018-10-15 15:36:50 +0100
committerDan Kegel <dank@kegel.com>2020-06-05 14:15:32 -0700
commitf9c03dfd292913d4d6d1560911ce8b58ab29f22e (patch)
tree6b1d25772e606f492591da884e713f0b360b1e97
parent50f98f3726a920a858bab5a7e7d6334813fc6048 (diff)
downloadmeson-f9c03dfd292913d4d6d1560911ce8b58ab29f22e.zip
meson-f9c03dfd292913d4d6d1560911ce8b58ab29f22e.tar.gz
meson-f9c03dfd292913d4d6d1560911ce8b58ab29f22e.tar.bz2
ninja: Only use response files when needed
Writing rsp files on Windows is moderately expensive, so only use them when the command line is long enough to need them. This also makes the output of 'ninja -v' useful more often (something like 'cl @exec@exe/main.c.obj.rsp' is not very useful if you don't have the response file to look at) For a rule where using a rspfile is possible, write rspfile and non-rspfile versions of that rule. Choose which one to use for each build statement, depending on the anticpated length of the command line.
-rw-r--r--mesonbuild/backend/ninjabackend.py92
1 files changed, 72 insertions, 20 deletions
diff --git a/mesonbuild/backend/ninjabackend.py b/mesonbuild/backend/ninjabackend.py
index e6eb0ec..b9378b8 100644
--- a/mesonbuild/backend/ninjabackend.py
+++ b/mesonbuild/backend/ninjabackend.py
@@ -57,6 +57,9 @@ else:
execute_wrapper = []
rmfile_prefix = ['rm', '-f', '{}', '&&']
+# a conservative estimate of the command-line length limit on windows
+rsp_threshold = 4096
+
def ninja_quote(text, is_build_line=False):
if is_build_line:
qcs = ('$', ' ', ':')
@@ -101,24 +104,54 @@ class NinjaRule:
if not self.refcount:
return
- outfile.write('rule {}\n'.format(self.name))
- if self.rspable:
- outfile.write(' command = {} @$out.rsp\n'.format(' '.join(self.command)))
- outfile.write(' rspfile = $out.rsp\n')
- outfile.write(' rspfile_content = {}\n'.format(' '.join(self.args)))
- else:
- outfile.write(' command = {}\n'.format(' '.join(self.command + self.args)))
- if self.deps:
- outfile.write(' deps = {}\n'.format(self.deps))
- if self.depfile:
- outfile.write(' depfile = {}\n'.format(self.depfile))
- outfile.write(' description = {}\n'.format(self.description))
- if self.extra:
- for l in self.extra.split('\n'):
- outfile.write(' ')
- outfile.write(l)
- outfile.write('\n')
- outfile.write('\n')
+ for rsp in [''] + (['_RSP'] if self.rspable else []):
+ outfile.write('rule {}{}\n'.format(self.name, rsp))
+ if self.rspable:
+ outfile.write(' command = {} @$out.rsp\n'.format(' '.join(self.command)))
+ outfile.write(' rspfile = $out.rsp\n')
+ outfile.write(' rspfile_content = {}\n'.format(' '.join(self.args)))
+ else:
+ outfile.write(' command = {}\n'.format(' '.join(self.command + self.args)))
+ if self.deps:
+ outfile.write(' deps = {}\n'.format(self.deps))
+ if self.depfile:
+ outfile.write(' depfile = {}\n'.format(self.depfile))
+ outfile.write(' description = {}\n'.format(self.description))
+ if self.extra:
+ for l in self.extra.split('\n'):
+ outfile.write(' ')
+ outfile.write(l)
+ outfile.write('\n')
+ outfile.write('\n')
+
+ def length_estimate(self, infiles, outfiles, elems):
+ # determine variables
+ # this order of actions only approximates ninja's scoping rules, as
+ # documented at: https://ninja-build.org/manual.html#ref_scope
+ ninja_vars = {}
+ for e in elems:
+ (name, value) = e
+ ninja_vars[name] = value
+ ninja_vars['deps'] = self.deps
+ ninja_vars['depfile'] = self.depfile
+ ninja_vars['in'] = infiles
+ ninja_vars['out'] = outfiles
+
+ # expand variables in command (XXX: this ignores any escaping/quoting
+ # that NinjaBuildElement.write() might do)
+ command = ' '.join(self.command + self.args)
+ expanded_command = ''
+ for m in re.finditer(r'(\${\w*})|(\$\w*)|([^$]*)', command):
+ chunk = m.group()
+ if chunk.startswith('$'):
+ chunk = chunk[1:]
+ chunk = re.sub(r'{(.*)}', r'\1', chunk)
+ chunk = ninja_vars.get(chunk, []) # undefined ninja variables are empty
+ chunk = ' '.join(chunk)
+ expanded_command += chunk
+
+ # determine command length
+ return len(expanded_command)
class NinjaBuildElement:
def __init__(self, all_outputs, outfilenames, rulename, infilenames, implicit_outs=None):
@@ -159,6 +192,17 @@ class NinjaBuildElement:
elems = [elems]
self.elems.append((name, elems))
+ def _should_use_rspfile(self, infiles, outfiles):
+ # 'phony' is a rule built-in to ninja
+ if self.rulename == 'phony':
+ return False
+
+ if not self.rule.rspable:
+ return False
+
+ return self.rule.length_estimate(infiles, outfiles,
+ self.elems) >= rsp_threshold
+
def write(self, outfile):
self.check_outputs()
ins = ' '.join([ninja_quote(i, True) for i in self.infilenames])
@@ -166,7 +210,12 @@ class NinjaBuildElement:
implicit_outs = ' '.join([ninja_quote(i, True) for i in self.implicit_outfilenames])
if implicit_outs:
implicit_outs = ' | ' + implicit_outs
- line = 'build {}{}: {} {}'.format(outs, implicit_outs, self.rulename, ins)
+ if self._should_use_rspfile(ins, outs):
+ rulename = self.rulename + '_RSP'
+ mlog.log("Command line for building %s is long, using a response file" % self.outfilenames)
+ else:
+ rulename = self.rulename
+ line = 'build {}{}: {} {}'.format(outs, implicit_outs, rulename, ins)
if len(self.deps) > 0:
line += ' | ' + ' '.join([ninja_quote(x, True) for x in self.deps])
if len(self.orderdeps) > 0:
@@ -900,10 +949,13 @@ int dummy;
def add_build(self, build):
self.build_elements.append(build)
- # increment rule refcount
if build.rulename != 'phony':
+ # increment rule refcount
self.ruledict[build.rulename].refcount += 1
+ # reference rule
+ build.rule = self.ruledict[build.rulename]
+
def write_rules(self, outfile):
for r in self.rules:
r.write(outfile)