]> Git Repo - qemu.git/blob - scripts/ninjatool.py
hw/mips/fuloong2e: Set CPU frequency to 533 MHz
[qemu.git] / scripts / ninjatool.py
1 #! /bin/sh
2
3 # Python module for parsing and processing .ninja files.
4 #
5 # Author: Paolo Bonzini
6 #
7 # Copyright (C) 2019 Red Hat, Inc.
8
9
10 # We don't want to put "#! @PYTHON@" as the shebang and
11 # make the file executable, so instead we make this a
12 # Python/shell polyglot.  The first line below starts a
13 # multiline string literal for Python, while it is just
14 # ":" for bash.  The closing of the multiline string literal
15 # is never parsed by bash since it exits before.
16
17 '''':
18 case "$0" in
19   /*) me=$0 ;;
20   *) me=$(command -v "$0") ;;
21 esac
22 python="@PYTHON@"
23 case $python in
24   @*) python=python3 ;;
25 esac
26 exec $python "$me" "$@"
27 exit 1
28 '''
29
30
31 from collections import namedtuple, defaultdict
32 import sys
33 import os
34 import re
35 import json
36 import argparse
37 import hashlib
38 import shutil
39
40
41 class InvalidArgumentError(Exception):
42     pass
43
44 # faster version of os.path.normpath: do nothing unless there is a double
45 # slash or a "." or ".." component.  The filter does not have to be super
46 # precise, but it has to be fast.  os.path.normpath is the hottest function
47 # for ninja2make without this optimization!
48 if os.path.sep == '/':
49     def normpath(path, _slow_re=re.compile('/[./]')):
50         return os.path.normpath(path) if _slow_re.search(path) or path[0] == '.' else path
51 else:
52     normpath = os.path.normpath
53
54
55 def sha1_text(text):
56     return hashlib.sha1(text.encode()).hexdigest()
57
58 # ---- lexer and parser ----
59
60 PATH_RE = r"[^$\s:|]+|\$[$ :]|\$[a-zA-Z0-9_-]+|\$\{[a-zA-Z0-9_.-]+\}"
61
62 SIMPLE_PATH_RE = re.compile(r"^[^$\s:|]+$")
63 IDENT_RE = re.compile(r"[a-zA-Z0-9_.-]+$")
64 STRING_RE = re.compile(r"(" + PATH_RE + r"|[\s:|])(?:\r?\n)?|.")
65 TOPLEVEL_RE = re.compile(r"([=:#]|\|\|?|^ +|(?:" + PATH_RE + r")+)\s*|.")
66 VAR_RE=re.compile(r'\$\$|\$\{([^}]*)\}')
67
68 BUILD = 1
69 POOL = 2
70 RULE = 3
71 DEFAULT = 4
72 EQUALS = 5
73 COLON = 6
74 PIPE = 7
75 PIPE2 = 8
76 IDENT = 9
77 INCLUDE = 10
78 INDENT = 11
79 EOL = 12
80
81
82 class LexerError(Exception):
83     pass
84
85
86 class ParseError(Exception):
87     pass
88
89
90 class NinjaParserEvents(object):
91     def __init__(self, parser):
92         self.parser = parser
93
94     def dollar_token(self, word, in_path=False):
95         return '$$' if word == '$' else word
96
97     def variable_expansion_token(self, varname):
98         return '${%s}' % varname
99
100     def variable(self, name, arg):
101         pass
102
103     def begin_file(self):
104         pass
105
106     def end_file(self):
107         pass
108
109     def end_scope(self):
110         pass
111
112     def begin_pool(self, name):
113         pass
114
115     def begin_rule(self, name):
116         pass
117
118     def begin_build(self, out, iout, rule, in_, iin, orderdep):
119         pass
120
121     def default(self, targets):
122         pass
123
124
125 class NinjaParser(object):
126
127     InputFile = namedtuple('InputFile', 'filename iter lineno')
128
129     def __init__(self, filename, input):
130         self.stack = []
131         self.top = None
132         self.iter = None
133         self.lineno = None
134         self.match_keyword = False
135         self.push(filename, input)
136
137     def file_changed(self):
138         self.iter = self.top.iter
139         self.lineno = self.top.lineno
140         if self.top.filename is not None:
141             os.chdir(os.path.dirname(self.top.filename) or '.')
142
143     def push(self, filename, input):
144         if self.top:
145             self.top.lineno = self.lineno
146             self.top.iter = self.iter
147             self.stack.append(self.top)
148         self.top = self.InputFile(filename=filename or 'stdin',
149                                   iter=self._tokens(input), lineno=0)
150         self.file_changed()
151
152     def pop(self):
153         if len(self.stack):
154             self.top = self.stack[-1]
155             self.stack.pop()
156             self.file_changed()
157         else:
158             self.top = self.iter = None
159
160     def next_line(self, input):
161         line = next(input).rstrip()
162         self.lineno += 1
163         while len(line) and line[-1] == '$':
164             line = line[0:-1] + next(input).strip()
165             self.lineno += 1
166         return line
167
168     def print_token(self, tok):
169         if tok == EOL:
170             return "end of line"
171         if tok == BUILD:
172             return '"build"'
173         if tok == POOL:
174             return '"pool"'
175         if tok == RULE:
176             return '"rule"'
177         if tok == DEFAULT:
178             return '"default"'
179         if tok == EQUALS:
180             return '"="'
181         if tok == COLON:
182             return '":"'
183         if tok == PIPE:
184             return '"|"'
185         if tok == PIPE2:
186             return '"||"'
187         if tok == INCLUDE:
188             return '"include"'
189         if tok == IDENT:
190             return 'identifier'
191         return '"%s"' % tok
192
193     def error(self, msg):
194         raise LexerError("%s:%d: %s" % (self.stack[-1].filename, self.lineno, msg))
195
196     def parse_error(self, msg):
197         raise ParseError("%s:%d: %s" % (self.stack[-1].filename, self.lineno, msg))
198
199     def expected(self, expected, tok):
200         msg = "found %s, expected " % (self.print_token(tok), )
201         for i, exp_tok in enumerate(expected):
202             if i > 0:
203                 msg = msg + (' or ' if i == len(expected) - 1 else ', ')
204             msg = msg + self.print_token(exp_tok)
205         self.parse_error(msg)
206
207     def _variable_tokens(self, value):
208         for m in STRING_RE.finditer(value):
209             match = m.group(1)
210             if not match:
211                 self.error("unexpected '%s'" % (m.group(0), ))
212             yield match
213
214     def _tokens(self, input):
215         while True:
216             try:
217                 line = self.next_line(input)
218             except StopIteration:
219                 return
220             for m in TOPLEVEL_RE.finditer(line):
221                 match = m.group(1)
222                 if not match:
223                     self.error("unexpected '%s'" % (m.group(0), ))
224                 if match == ':':
225                     yield COLON
226                     continue
227                 if match == '|':
228                     yield PIPE
229                     continue
230                 if match == '||':
231                     yield PIPE2
232                     continue
233                 if match[0] == ' ':
234                     yield INDENT
235                     continue
236                 if match[0] == '=':
237                     yield EQUALS
238                     value = line[m.start() + 1:].lstrip()
239                     yield from self._variable_tokens(value)
240                     break
241                 if match[0] == '#':
242                     break
243
244                 # identifier
245                 if self.match_keyword:
246                     if match == 'build':
247                         yield BUILD
248                         continue
249                     if match == 'pool':
250                         yield POOL
251                         continue
252                     if match == 'rule':
253                         yield RULE
254                         continue
255                     if match == 'default':
256                         yield DEFAULT
257                         continue
258                     if match == 'include':
259                         filename = line[m.start() + 8:].strip()
260                         self.push(filename, open(filename, 'r'))
261                         break
262                     if match == 'subninja':
263                         self.error('subninja is not supported')
264                 yield match
265             yield EOL
266
267     def parse(self, events):
268         global_var = True
269
270         def look_for(*expected):
271             # The last token in the token stream is always EOL.  This
272             # is exploited to avoid catching StopIteration everywhere.
273             tok = next(self.iter)
274             if tok not in expected:
275                 self.expected(expected, tok)
276             return tok
277
278         def look_for_ident(*expected):
279             tok = next(self.iter)
280             if isinstance(tok, str):
281                 if not IDENT_RE.match(tok):
282                     self.parse_error('variable expansion not allowed')
283             elif tok not in expected:
284                 self.expected(expected + (IDENT,), tok)
285             return tok
286
287         def parse_assignment_rhs(gen, expected, in_path):
288             tokens = []
289             for tok in gen:
290                 if not isinstance(tok, str):
291                     if tok in expected:
292                         break
293                     self.expected(expected + (IDENT,), tok)
294                 if tok[0] != '$':
295                     tokens.append(tok)
296                 elif tok == '$ ' or tok == '$$' or tok == '$:':
297                     tokens.append(events.dollar_token(tok[1], in_path))
298                 else:
299                     var = tok[2:-1] if tok[1] == '{' else tok[1:]
300                     tokens.append(events.variable_expansion_token(var))
301             else:
302                 # gen must have raised StopIteration
303                 tok = None
304
305             if tokens:
306                 # Fast path avoiding str.join()
307                 value = tokens[0] if len(tokens) == 1 else ''.join(tokens)
308             else:
309                 value = None
310             return value, tok
311
312         def look_for_path(*expected):
313             # paths in build rules are parsed one space-separated token
314             # at a time and expanded
315             token = next(self.iter)
316             if not isinstance(token, str):
317                 return None, token
318             # Fast path if there are no dollar and variable expansion
319             if SIMPLE_PATH_RE.match(token):
320                 return token, None
321             gen = self._variable_tokens(token)
322             return parse_assignment_rhs(gen, expected, True)
323
324         def parse_assignment(tok):
325             name = tok
326             assert isinstance(name, str)
327             look_for(EQUALS)
328             value, tok = parse_assignment_rhs(self.iter, (EOL,), False)
329             assert tok == EOL
330             events.variable(name, value)
331
332         def parse_build():
333             # parse outputs
334             out = []
335             iout = []
336             while True:
337                 value, tok = look_for_path(COLON, PIPE)
338                 if value is None:
339                     break
340                 out.append(value)
341             if tok == PIPE:
342                 while True:
343                     value, tok = look_for_path(COLON)
344                     if value is None:
345                         break
346                     iout.append(value)
347
348             # parse rule
349             assert tok == COLON
350             rule = look_for_ident()
351
352             # parse inputs and dependencies
353             in_ = []
354             iin = []
355             orderdep = []
356             while True:
357                 value, tok = look_for_path(PIPE, PIPE2, EOL)
358                 if value is None:
359                     break
360                 in_.append(value)
361             if tok == PIPE:
362                 while True:
363                     value, tok = look_for_path(PIPE2, EOL)
364                     if value is None:
365                         break
366                     iin.append(value)
367             if tok == PIPE2:
368                 while True:
369                     value, tok = look_for_path(EOL)
370                     if value is None:
371                         break
372                     orderdep.append(value)
373             assert tok == EOL
374             events.begin_build(out, iout, rule, in_, iin, orderdep)
375             nonlocal global_var
376             global_var = False
377
378         def parse_pool():
379             # pool declarations are ignored.  Just gobble all the variables
380             ident = look_for_ident()
381             look_for(EOL)
382             events.begin_pool(ident)
383             nonlocal global_var
384             global_var = False
385
386         def parse_rule():
387             ident = look_for_ident()
388             look_for(EOL)
389             events.begin_rule(ident)
390             nonlocal global_var
391             global_var = False
392
393         def parse_default():
394             idents = []
395             while True:
396                 ident = look_for_ident(EOL)
397                 if ident == EOL:
398                     break
399                 idents.append(ident)
400             events.default(idents)
401
402         def parse_declaration(tok):
403             if tok == EOL:
404                 return
405
406             nonlocal global_var
407             if tok == INDENT:
408                 if global_var:
409                     self.parse_error('indented line outside rule or edge')
410                 tok = look_for_ident(EOL)
411                 if tok == EOL:
412                     return
413                 parse_assignment(tok)
414                 return
415
416             if not global_var:
417                 events.end_scope()
418                 global_var = True
419             if tok == POOL:
420                 parse_pool()
421             elif tok == BUILD:
422                 parse_build()
423             elif tok == RULE:
424                 parse_rule()
425             elif tok == DEFAULT:
426                 parse_default()
427             elif isinstance(tok, str):
428                 parse_assignment(tok)
429             else:
430                 self.expected((POOL, BUILD, RULE, INCLUDE, DEFAULT, IDENT), tok)
431
432         events.begin_file()
433         while self.iter:
434             try:
435                 self.match_keyword = True
436                 token = next(self.iter)
437                 self.match_keyword = False
438                 parse_declaration(token)
439             except StopIteration:
440                 self.pop()
441         events.end_file()
442
443
444 # ---- variable handling ----
445
446 def expand(x, rule_vars=None, build_vars=None, global_vars=None):
447     if x is None:
448         return None
449     changed = True
450     have_dollar_replacement = False
451     while changed:
452         changed = False
453         matches = list(VAR_RE.finditer(x))
454         if not matches:
455             break
456
457         # Reverse the match so that expanding later matches does not
458         # invalidate m.start()/m.end() for earlier ones.  Do not reduce $$ to $
459         # until all variables are dealt with.
460         for m in reversed(matches):
461             name = m.group(1)
462             if not name:
463                 have_dollar_replacement = True
464                 continue
465             changed = True
466             if build_vars and name in build_vars:
467                 value = build_vars[name]
468             elif rule_vars and name in rule_vars:
469                 value = rule_vars[name]
470             elif name in global_vars:
471                 value = global_vars[name]
472             else:
473                 value = ''
474             x = x[:m.start()] + value + x[m.end():]
475     return x.replace('$$', '$') if have_dollar_replacement else x
476
477
478 class Scope(object):
479     def __init__(self, events):
480         self.events = events
481
482     def on_left_scope(self):
483         pass
484
485     def on_variable(self, key, value):
486         pass
487
488
489 class BuildScope(Scope):
490     def __init__(self, events, out, iout, rule, in_, iin, orderdep, rule_vars):
491         super().__init__(events)
492         self.rule = rule
493         self.out = [events.expand_and_normalize(x) for x in out]
494         self.in_ = [events.expand_and_normalize(x) for x in in_]
495         self.iin = [events.expand_and_normalize(x) for x in iin]
496         self.orderdep = [events.expand_and_normalize(x) for x in orderdep]
497         self.iout = [events.expand_and_normalize(x) for x in iout]
498         self.rule_vars = rule_vars
499         self.build_vars = dict()
500         self._define_variable('out', ' '.join(self.out))
501         self._define_variable('in', ' '.join(self.in_))
502
503     def expand(self, x):
504         return self.events.expand(x, self.rule_vars, self.build_vars)
505
506     def on_left_scope(self):
507         self.events.variable('out', self.build_vars['out'])
508         self.events.variable('in', self.build_vars['in'])
509         self.events.end_build(self, self.out, self.iout, self.rule, self.in_,
510                               self.iin, self.orderdep)
511
512     def _define_variable(self, key, value):
513         # The value has been expanded already, quote it for further
514         # expansion from rule variables
515         value = value.replace('$', '$$')
516         self.build_vars[key] = value
517
518     def on_variable(self, key, value):
519         # in and out are at the top of the lookup order and cannot
520         # be overridden.  Also, unlike what the manual says, build
521         # variables only lookup global variables.  They never lookup
522         # rule variables, earlier build variables, or in/out.
523         if key not in ('in', 'in_newline', 'out'):
524             self._define_variable(key, self.events.expand(value))
525
526
527 class RuleScope(Scope):
528     def __init__(self, events, name, vars_dict):
529         super().__init__(events)
530         self.name = name
531         self.vars_dict = vars_dict
532         self.generator = False
533
534     def on_left_scope(self):
535         self.events.end_rule(self, self.name)
536
537     def on_variable(self, key, value):
538         self.vars_dict[key] = value
539         if key == 'generator':
540             self.generator = True
541
542
543 class NinjaParserEventsWithVars(NinjaParserEvents):
544     def __init__(self, parser):
545         super().__init__(parser)
546         self.rule_vars = defaultdict(lambda: dict())
547         self.global_vars = dict()
548         self.scope = None
549
550     def variable(self, name, value):
551         if self.scope:
552             self.scope.on_variable(name, value)
553         else:
554             self.global_vars[name] = self.expand(value)
555
556     def begin_build(self, out, iout, rule, in_, iin, orderdep):
557         if rule != 'phony' and rule not in self.rule_vars:
558             self.parser.parse_error("undefined rule '%s'" % rule)
559
560         self.scope = BuildScope(self, out, iout, rule, in_, iin, orderdep, self.rule_vars[rule])
561
562     def begin_pool(self, name):
563         # pool declarations are ignored.  Just gobble all the variables
564         self.scope = Scope(self)
565
566     def begin_rule(self, name):
567         if name in self.rule_vars:
568             self.parser.parse_error("duplicate rule '%s'" % name)
569         self.scope = RuleScope(self, name, self.rule_vars[name])
570
571     def end_scope(self):
572         self.scope.on_left_scope()
573         self.scope = None
574
575     # utility functions:
576
577     def expand(self, x, rule_vars=None, build_vars=None):
578         return expand(x, rule_vars, build_vars, self.global_vars)
579
580     def expand_and_normalize(self, x):
581         return normpath(self.expand(x))
582
583     # extra events not present in the superclass:
584
585     def end_build(self, scope, out, iout, rule, in_, iin, orderdep):
586         pass
587
588     def end_rule(self, scope, name):
589         pass
590
591
592 # ---- test client that just prints back whatever it parsed  ----
593
594 class Writer(NinjaParserEvents):
595     ARGS = argparse.ArgumentParser(description='Rewrite input build.ninja to stdout.')
596
597     def __init__(self, output, parser, args):
598         super().__init__(parser)
599         self.output = output
600         self.indent = ''
601         self.had_vars = False
602
603     def dollar_token(self, word, in_path=False):
604         return '$' + word
605
606     def print(self, *args, **kwargs):
607         if len(args):
608             self.output.write(self.indent)
609         print(*args, **kwargs, file=self.output)
610
611     def variable(self, name, value):
612         self.print('%s = %s' % (name, value))
613         self.had_vars = True
614
615     def begin_scope(self):
616         self.indent = '  '
617         self.had_vars = False
618
619     def end_scope(self):
620         if self.had_vars:
621             self.print()
622         self.indent = ''
623         self.had_vars = False
624
625     def begin_pool(self, name):
626         self.print('pool %s' % name)
627         self.begin_scope()
628
629     def begin_rule(self, name):
630         self.print('rule %s' % name)
631         self.begin_scope()
632
633     def begin_build(self, outputs, implicit_outputs, rule, inputs, implicit, order_only):
634         all_outputs = list(outputs)
635         all_inputs = list(inputs)
636
637         if implicit:
638             all_inputs.append('|')
639             all_inputs.extend(implicit)
640         if order_only:
641             all_inputs.append('||')
642             all_inputs.extend(order_only)
643         if implicit_outputs:
644             all_outputs.append('|')
645             all_outputs.extend(implicit_outputs)
646
647         self.print('build %s: %s' % (' '.join(all_outputs),
648                                      ' '.join([rule] + all_inputs)))
649         self.begin_scope()
650
651     def default(self, targets):
652         self.print('default %s' % ' '.join(targets))
653
654
655 # ---- emit compile_commands.json ----
656
657 class Compdb(NinjaParserEventsWithVars):
658     ARGS = argparse.ArgumentParser(description='Emit compile_commands.json.')
659     ARGS.add_argument('rules', nargs='*',
660                       help='The ninja rules to emit compilation commands for.')
661
662     def __init__(self, output, parser, args):
663         super().__init__(parser)
664         self.output = output
665         self.rules = args.rules
666         self.sep = ''
667
668     def begin_file(self):
669         self.output.write('[')
670         self.directory = os.getcwd()
671
672     def print_entry(self, **entry):
673         entry['directory'] = self.directory
674         self.output.write(self.sep + json.dumps(entry))
675         self.sep = ',\n'
676
677     def begin_build(self, out, iout, rule, in_, iin, orderdep):
678         if in_ and rule in self.rules:
679             super().begin_build(out, iout, rule, in_, iin, orderdep)
680         else:
681             self.scope = Scope(self)
682
683     def end_build(self, scope, out, iout, rule, in_, iin, orderdep):
684         self.print_entry(command=scope.expand('${command}'), file=in_[0])
685
686     def end_file(self):
687         self.output.write(']\n')
688
689
690 # ---- clean output files ----
691
692 class Clean(NinjaParserEventsWithVars):
693     ARGS = argparse.ArgumentParser(description='Remove output build files.')
694     ARGS.add_argument('-g', dest='generator', action='store_true',
695                       help='clean generated files too')
696
697     def __init__(self, output, parser, args):
698         super().__init__(parser)
699         self.dry_run = args.dry_run
700         self.verbose = args.verbose or args.dry_run
701         self.generator = args.generator
702
703     def begin_file(self):
704         print('Cleaning... ', end=(None if self.verbose else ''), flush=True)
705         self.cnt = 0
706
707     def end_file(self):
708         print('%d files' % self.cnt)
709
710     def do_clean(self, *files):
711         for f in files:
712             if self.dry_run:
713                 if os.path.exists(f):
714                     self.cnt += 1
715                     print('Would remove ' + f)
716                     continue
717             else:
718                 try:
719                     if os.path.isdir(f):
720                         shutil.rmtree(f)
721                     else:
722                         os.unlink(f)
723                     self.cnt += 1
724                     if self.verbose:
725                         print('Removed ' + f)
726                 except FileNotFoundError:
727                     pass
728
729     def end_build(self, scope, out, iout, rule, in_, iin, orderdep):
730         if rule == 'phony':
731             return
732         if self.generator:
733             rspfile = scope.expand('${rspfile}')
734             if rspfile:
735                 self.do_clean(rspfile)
736         if self.generator or not scope.expand('${generator}'):
737             self.do_clean(*out, *iout)
738             depfile = scope.expand('${depfile}')
739             if depfile:
740                 self.do_clean(depfile)
741
742
743 # ---- convert build.ninja to makefile ----
744
745 class Ninja2Make(NinjaParserEventsWithVars):
746     ARGS = argparse.ArgumentParser(description='Convert build.ninja to a Makefile.')
747     ARGS.add_argument('--clean', dest='emit_clean', action='store_true',
748                       help='Emit clean/distclean rules.')
749     ARGS.add_argument('--doublecolon', action='store_true',
750                       help='Emit double-colon rules for phony targets.')
751     ARGS.add_argument('--omit', metavar='TARGET', nargs='+',
752                       help='Targets to omit.')
753
754     def __init__(self, output, parser, args):
755         super().__init__(parser)
756         self.output = output
757
758         self.emit_clean = args.emit_clean
759         self.doublecolon = args.doublecolon
760         self.omit = set(args.omit)
761
762         if self.emit_clean:
763             self.omit.update(['clean', 'distclean'])
764
765         # Lists of targets are kept in memory and emitted only at the
766         # end because appending is really inefficient in GNU make.
767         # We only do it when it's O(#rules) or O(#variables), but
768         # never when it could be O(#targets).
769         self.depfiles = list()
770         self.rspfiles = list()
771         self.build_vars = defaultdict(lambda: dict())
772         self.rule_targets = defaultdict(lambda: list())
773         self.stamp_targets = defaultdict(lambda: list())
774         self.all_outs = set()
775         self.all_ins = set()
776         self.all_phony = set()
777         self.seen_default = False
778
779     def print(self, *args, **kwargs):
780         print(*args, **kwargs, file=self.output)
781
782     def dollar_token(self, word, in_path=False):
783         if in_path and word == ' ':
784             self.parser.parse_error('Make does not support spaces in filenames')
785         return '$$' if word == '$' else word
786
787     def print_phony(self, outs, ins):
788         targets = ' '.join(outs).replace('$', '$$')
789         deps = ' '.join(ins).replace('$', '$$')
790         deps = deps.strip()
791         if self.doublecolon:
792             self.print(targets + '::' + (' ' if deps else '') + deps + ';@:')
793         else:
794             self.print(targets + ':' + (' ' if deps else '') + deps)
795         self.all_phony.update(outs)
796
797     def begin_file(self):
798         self.print(r'# This is an automatically generated file, and it shows.')
799         self.print(r'ninja-default:')
800         self.print(r'.PHONY: ninja-default ninja-clean ninja-distclean')
801         if self.emit_clean:
802             self.print(r'ninja-clean:: ninja-clean-start; $(if $V,,@)rm -f ${ninja-depfiles}')
803             self.print(r'ninja-clean-start:; $(if $V,,@echo Cleaning...)')
804             self.print(r'ninja-distclean:: clean; $(if $V,,@)rm -f ${ninja-rspfiles}')
805             self.print(r'.PHONY: ninja-clean-start')
806             self.print_phony(['clean'], ['ninja-clean'])
807             self.print_phony(['distclean'], ['ninja-distclean'])
808         self.print(r'vpath')
809         self.print(r'NULL :=')
810         self.print(r'SPACE := ${NULL} #')
811         self.print(r'MAKEFLAGS += -rR')
812         self.print(r'define NEWLINE')
813         self.print(r'')
814         self.print(r'endef')
815         self.print(r'.var.in_newline = $(subst $(SPACE),$(NEWLINE),${.var.in})')
816         self.print(r"ninja-command = $(if $V,,$(if ${.var.description},@printf '%s\n' '$(subst ','\'',${.var.description})' && ))${.var.command}")
817         self.print(r"ninja-command-restat = $(if $V,,$(if ${.var.description},@printf '%s\n' '$(subst ','\'',${.var.description})' && ))${.var.command} && if test -e $(firstword ${.var.out}); then printf '%s\n' ${.var.out} > $@; fi")
818
819     def end_file(self):
820         def natural_sort_key(s, _nsre=re.compile('([0-9]+)')):
821             return [int(text) if text.isdigit() else text.lower()
822                     for text in _nsre.split(s)]
823
824         self.print()
825         self.print('ninja-outputdirs :=')
826         for rule in self.rule_vars:
827             if rule == 'phony':
828                 continue
829             self.print('ninja-targets-%s := %s' % (rule, ' '.join(self.rule_targets[rule])))
830             self.print('ninja-stamp-%s := %s' % (rule, ' '.join(self.stamp_targets[rule])))
831             self.print('ninja-outputdirs += $(sort $(dir ${ninja-targets-%s}))' % rule)
832             self.print()
833         self.print('dummy := $(shell mkdir -p . $(sort $(ninja-outputdirs)))')
834         self.print('ninja-depfiles :=' + ' '.join(self.depfiles))
835         self.print('ninja-rspfiles :=' + ' '.join(self.rspfiles))
836         self.print('-include ${ninja-depfiles}')
837         self.print()
838         for targets in self.build_vars:
839             for name, value in self.build_vars[targets].items():
840                 self.print('%s: private .var.%s := %s' %
841                            (targets, name, value.replace('$', '$$')))
842             self.print()
843         if not self.seen_default:
844             default_targets = sorted(self.all_outs - self.all_ins, key=natural_sort_key)
845             self.print('ninja-default: ' + ' '.join(default_targets))
846
847         # This is a hack...  Meson declares input meson.build files as
848         # phony, because Ninja does not have an equivalent of Make's
849         # "path/to/file:" declaration that ignores "path/to/file" even
850         # if it is absent.  However, Makefile.ninja wants to depend on
851         # build.ninja, which in turn depends on these phony targets which
852         # would cause Makefile.ninja to be rebuilt in a loop.
853         phony_targets = sorted(self.all_phony - self.all_ins, key=natural_sort_key)
854         self.print('.PHONY: ' + ' '.join(phony_targets))
855
856     def variable(self, name, value):
857         super().variable(name, value)
858         if self.scope is None:
859             self.global_vars[name] = self.expand(value)
860             self.print('.var.%s := %s' % (name, self.global_vars[name]))
861
862     def begin_build(self, out, iout, rule, in_, iin, orderdep):
863         if any(x in self.omit for x in out):
864             self.scope = Scope(self)
865             return
866
867         super().begin_build(out, iout, rule, in_, iin, orderdep)
868         self.current_targets = ' '.join(self.scope.out + self.scope.iout).replace('$', '$$')
869
870     def end_build(self, scope, out, iout, rule, in_, iin, orderdep):
871         self.rule_targets[rule] += self.scope.out
872         self.rule_targets[rule] += self.scope.iout
873
874         self.all_outs.update(self.scope.iout)
875         self.all_outs.update(self.scope.out)
876         self.all_ins.update(self.scope.in_)
877         self.all_ins.update(self.scope.iin)
878
879         targets = self.current_targets
880         self.current_targets = None
881         if rule == 'phony':
882             # Phony rules treat order-only dependencies as normal deps
883             self.print_phony(out + iout, in_ + iin + orderdep)
884             return
885
886         inputs = ' '.join(in_ + iin).replace('$', '$$')
887         orderonly = ' '.join(orderdep).replace('$', '$$')
888
889         rspfile = scope.expand('${rspfile}')
890         if rspfile:
891             rspfile_content = scope.expand('${rspfile_content}')
892             with open(rspfile, 'w') as f:
893                 f.write(rspfile_content)
894             inputs += ' ' + rspfile
895             self.rspfiles.append(rspfile)
896
897         restat = 'restat' in self.scope.build_vars or 'restat' in self.rule_vars[rule]
898         depfile = scope.expand('${depfile}')
899         build_vars = {
900             'command': scope.expand('${command}'),
901             'description': scope.expand('${description}'),
902             'out': scope.expand('${out}')
903         }
904
905         if restat and not depfile:
906             if len(out) == 1:
907                 stamp = out[0] + '.stamp'
908             else:
909                 stamp = '%s@%s.stamp' % (rule, sha1_text(targets)[0:11])
910             self.print('%s: %s; @:' % (targets, stamp))
911             self.print('ifneq (%s, $(wildcard %s))' % (targets, targets))
912             self.print('.PHONY: %s' % (stamp, ))
913             self.print('endif')
914             self.print('%s: %s | %s; ${ninja-command-restat}' % (stamp, inputs, orderonly))
915             self.rule_targets[rule].append(stamp)
916             self.stamp_targets[rule].append(stamp)
917             self.build_vars[stamp] = build_vars
918         else:
919             self.print('%s: %s | %s; ${ninja-command}' % (targets, inputs, orderonly))
920             self.build_vars[targets] = build_vars
921             if depfile:
922                 self.depfiles.append(depfile)
923
924     def end_rule(self, scope, name):
925         # Note that the generator pseudo-variable could also be attached
926         # to a build block rather than a rule.  This is not handled here
927         # in order to reduce the number of "rm" invocations.  However,
928         # "ninjatool.py -t clean" does that correctly.
929         target = 'distclean' if scope.generator else 'clean'
930         self.print('ninja-%s:: ; $(if $V,,@)rm -f ${ninja-stamp-%s}' % (target, name))
931         if self.emit_clean:
932             self.print('ninja-%s:: ; $(if $V,,@)rm -rf ${ninja-targets-%s}' % (target, name))
933
934     def default(self, targets):
935         self.print("ninja-default: " + ' '.join(targets))
936         self.seen_default = True
937
938
939 # ---- command line parsing ----
940
941 # we cannot use subparsers because tools are chosen through the "-t"
942 # option.
943
944 class ToolAction(argparse.Action):
945     def __init__(self, option_strings, dest, choices, metavar='TOOL', nargs=None, **kwargs):
946         if nargs is not None:
947             raise ValueError("nargs not allowed")
948         super().__init__(option_strings, dest, required=True, choices=choices,
949                          metavar=metavar, **kwargs)
950
951     def __call__(self, parser, namespace, value, option_string):
952         tool = self.choices[value]
953         setattr(namespace, self.dest, tool)
954         tool.ARGS.prog = '%s %s %s' % (parser.prog, option_string, value)
955
956
957 class ToolHelpAction(argparse.Action):
958     def __init__(self, option_strings, dest, nargs=None, **kwargs):
959         if nargs is not None:
960             raise ValueError("nargs not allowed")
961         super().__init__(option_strings, dest, nargs=0, **kwargs)
962
963     def __call__(self, parser, namespace, values, option_string=None):
964         if namespace.tool:
965             namespace.tool.ARGS.print_help()
966         else:
967             parser.print_help()
968         parser.exit()
969
970
971 tools = {
972     'test': Writer,
973     'ninja2make': Ninja2Make,
974     'compdb': Compdb,
975     'clean': Clean,
976 }
977
978 parser = argparse.ArgumentParser(description='Process and transform build.ninja files.',
979                                  add_help=False)
980 parser.add_argument('-C', metavar='DIR', dest='dir', default='.',
981                     help='change to DIR before doing anything else')
982 parser.add_argument('-f', metavar='FILE', dest='file', default='build.ninja',
983                     help='specify input build file [default=build.ninja]')
984 parser.add_argument('-n', dest='dry_run', action='store_true',
985                     help='do not actually do anything')
986 parser.add_argument('-v', dest='verbose', action='store_true',
987                     help='be more verbose')
988
989 parser.add_argument('-t', dest='tool', choices=tools, action=ToolAction,
990                     help='choose the tool to run')
991 parser.add_argument('-h', '--help', action=ToolHelpAction,
992                     help='show this help message and exit')
993
994 if len(sys.argv) >= 2 and sys.argv[1] == '--version':
995     print('1.8')
996     sys.exit(0)
997
998 args, tool_args = parser.parse_known_args()
999 args.tool.ARGS.parse_args(tool_args, args)
1000
1001 os.chdir(args.dir)
1002 with open(args.file, 'r') as f:
1003     parser = NinjaParser(args.file, f)
1004     try:
1005         events = args.tool(sys.stdout, parser, args)
1006     except InvalidArgumentError as e:
1007         parser.error(str(e))
1008     parser.parse(events)
This page took 0.08189 seconds and 4 git commands to generate.