• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1#!/usr/bin/env python3
2
3"""Ninja File Parser.
4"""
5
6from __future__ import print_function
7
8import argparse
9import collections
10import os
11import re
12import struct
13import sys
14
15DEBUG_ALLOC = False
16
17if DEBUG_ALLOC:
18    try:
19        import tracemalloc
20    except ImportError:
21        DEBUG_ALLOC = False
22
23try:
24    from cStringIO import StringIO  # Python 2
25except ImportError:
26    from io import StringIO  # Python 3
27
28try:
29    from sys import intern
30except ImportError:
31    pass  # In Python 2, intern() is a built-in function.
32
33if sys.version_info < (3,):
34    # Wrap built-in open() function to ignore encoding in Python 2.
35    _builtin_open = open
36    def open(path, mode, encoding=None):
37        return _builtin_open(path, mode)
38
39    # Replace built-in zip() function with itertools.izip
40    from itertools import izip as zip
41
42
43class EvalEnv(dict):
44    __slots__ = ('parent')
45
46
47    def __init__(self, *args, **kwargs):
48        super(EvalEnv, self).__init__(*args, **kwargs)
49        self.parent = None
50
51
52    def get_recursive(self, key, default=None):
53        try:
54            return self[key]
55        except KeyError:
56            if self.parent:
57                return self.parent.get_recursive(key, default)
58            return default
59
60
61class BuildEvalEnv(EvalEnv):
62    __slots__ = ('_build_env', '_rule_env')
63
64
65    def __init__(self, build_env, rule_env):
66        self._build_env = build_env
67        self._rule_env = rule_env
68
69
70    def get_recursive(self, key, default=None):
71        try:
72            return self._build_env[key]
73        except KeyError:
74            pass
75
76        if self._rule_env:
77            try:
78                return self._rule_env[key]
79            except KeyError:
80                pass
81
82        if self._build_env.parent:
83            return self._build_env.parent.get_recursive(key, default)
84        return default
85
86
87class EvalError(ValueError):
88    """Exceptions for ``EvalString`` evalution errors."""
89    pass
90
91
92class EvalCircularError(EvalError):
93    """Exception for circular substitution in ``EvalString``."""
94
95
96    def __init__(self, expanded_vars):
97        super(EvalCircularError, self).__init__(
98                'circular evaluation: ' + ' -> '.join(expanded_vars))
99
100
101class EvalString(tuple):
102    """Strings with variables to be substituted."""
103
104
105    def __bool__(self):
106        """Check whether this is an empty string."""
107        return len(self) > 1
108
109
110    def __nonzero__(self):
111        """Check whether this is an empty string (Python2)."""
112        return self.__bool__()
113
114
115    def create_iters(self):
116        """Create descriptors and segments iterators."""
117        curr_iter = iter(self)
118        descs = next(curr_iter)
119        return zip(descs, curr_iter)
120
121
122def _eval_string(s, env, expanded_vars, result_buf):
123    """Evaluate each segments in ``EvalString`` and write result to the
124    given StringIO buffer.
125
126    Args:
127        env: A ``dict`` that maps a name to ``EvalString`` object.
128        expanded_vars: A ``list`` that keeps the variable under evaluation.
129        result_buf: Output buffer.
130    """
131    if type(s) is str:
132        result_buf.write(s)
133        return
134
135    for desc, seg in s.create_iters():
136        if desc == 't':
137            # Append raw text
138            result_buf.write(seg)
139        else:
140            # Substitute variables
141            varname = seg
142            if varname in expanded_vars:
143                raise EvalCircularError(expanded_vars + [varname])
144            expanded_vars.append(varname)
145            try:
146                next_es = env.get_recursive(varname)
147                if next_es:
148                    _eval_string(next_es, env, expanded_vars, result_buf)
149            finally:
150                expanded_vars.pop()
151
152
153def eval_string(s, env):
154    """Evaluate a ``str`` or ``EvalString`` in an environment.
155
156    Args:
157        env: A ``dict`` that maps a name to an ``EvalString`` object.
158
159    Returns:
160        str: The result of evaluation.
161
162    Raises:
163        EvalNameError: Unknown variable name occurs.
164        EvalCircularError: Circular variable substitution occurs.
165    """
166    expanded_vars = []
167    result_buf = StringIO()
168    _eval_string(s, env, expanded_vars, result_buf)
169    return result_buf.getvalue()
170
171
172def eval_path_strings(strs, env):
173    """Evalute a list of ``EvalString`` in an environment and normalize paths.
174
175    Args:
176        strs: A list of ``EvalString`` which should be treated as paths.
177        env: A ``dict`` that maps a name to an ``EvalString`` object.
178
179    Returns:
180        The list of evaluated strings.
181    """
182    return [intern(os.path.normpath(eval_string(s, env))) for s in strs]
183
184
185class EvalStringBuilder(object):
186    def __init__(self):
187        self._segs = ['']
188
189
190    def append_raw(self, text):
191        descs = self._segs[0]
192        if descs and descs[-1] == 't':
193            self._segs[-1] += text
194        else:
195            self._segs[0] += 't'
196            self._segs.append(text)
197        return self
198
199
200    def append_var(self, varname):
201        self._segs[0] += 'v'
202        self._segs.append(varname)
203        return self
204
205
206    def getvalue(self):
207        return EvalString(intern(seg) for seg in self._segs)
208
209
210class Build(object):
211    __slots__ = ('explicit_outs', 'implicit_outs', 'rule', 'explicit_ins',
212                 'implicit_ins', 'prerequisites', 'bindings',
213                 'depfile_implicit_ins')
214
215
216class Rule(object):
217    __slots__ = ('name', 'bindings')
218
219
220class Pool(object):
221    __slots__ = ('name', 'bindings')
222
223
224class Default(object):
225    __slots__ = ('outs')
226
227
228Token = collections.namedtuple('Token', 'kind line column value')
229
230
231class TK(object):
232    """Token ID enumerations."""
233
234    # Trivial tokens
235    EOF = 0
236    COMMENT = 1
237    NEWLINE = 2
238    SPACE = 3
239    ESC_NEWLINE = 4
240    IDENT = 5
241    PIPE2 = 6
242    PIPE = 7
243    COLON = 8
244    ASSIGN = 9
245
246    # Non-trivial tokens
247    PATH = 10
248    STRING = 11
249
250
251class TokenMatcher(object):
252    def __init__(self, patterns):
253        self._matcher = re.compile('|'.join('(' + p + ')' for k, p in patterns))
254        self._kinds = [k for k, p in patterns]
255
256
257    def match(self, buf, pos):
258        match = self._matcher.match(buf, pos)
259        if not match:
260            return None
261        return (self._kinds[match.lastindex - 1], match.start(), match.end())
262
263
264class ParseError(ValueError):
265    def __init__(self, path, line, column, reason=None):
266        self.path = path
267        self.line = line
268        self.column = column
269        self.reason = reason
270
271
272    def __repr__(self):
273        s = 'ParseError: {}:{}:{}'.format(self.path, self.line, self.column)
274        if self.reason:
275            s += ': ' + self.reason
276        return s
277
278
279class Lexer(object):
280    def __init__(self, lines_iterable, path='<stdin>', encoding='utf-8'):
281        self.encoding = encoding
282        self.path = path
283
284        self._line_iter = iter(lines_iterable)
285        self._line_buf = None
286        self._line = 0
287        self._line_pos = 0
288        self._line_end = 0
289
290        self._line_start = True
291
292        self._next_token = None
293        self._next_pos = None
294
295
296    def raise_error(self, reason=None):
297        raise ParseError(self.path, self._line, self._line_pos + 1, reason)
298
299
300    def _read_next_line(self):
301        try:
302            self._line_buf = next(self._line_iter)
303            self._line_pos = 0
304            self._line_end = len(self._line_buf)
305            self._line += 1
306            return True
307        except StopIteration:
308            self._line_buf = None
309            return False
310
311
312    def _ensure_line(self):
313        if self._line_buf and self._line_pos < self._line_end:
314            return True
315        return self._read_next_line()
316
317    _COMMENT_MATCHER = re.compile(r'[ \t]*(?:#[^\n]*)?(?=\n)')
318
319
320    def _ensure_non_comment_line(self):
321        if not self._ensure_line():
322            return False
323        # Match comments or spaces
324        match = self._COMMENT_MATCHER.match(self._line_buf)
325        if not match:
326            return True
327        # Move the cursor to the newline character
328        self._line_pos = match.end()
329        return True
330
331    _SPACE_MATCHER = re.compile(r'[ \t]+')
332
333
334    def _skip_space(self):
335        match = self._SPACE_MATCHER.match(self._line_buf, self._line_pos)
336        if match:
337            self._line_pos = match.end()
338
339    _SIMPLE_TOKEN_MATCHER = TokenMatcher([
340        (TK.COMMENT, r'#[^\n]*'),
341        (TK.NEWLINE, r'[\r\n]'),
342        (TK.SPACE, r'[ \t]+'),
343        (TK.ESC_NEWLINE, r'\$[\r\n]'),
344        (TK.IDENT, r'[\w_.-]+'),
345        (TK.PIPE2, r'\|\|'),
346        (TK.PIPE, r'\|'),
347        (TK.COLON, r':'),
348        (TK.ASSIGN, r'='),
349    ])
350
351
352    def peek(self):
353        if self._next_token is not None:
354            return self._next_token
355        while True:
356            if not self._ensure_non_comment_line():
357                return Token(TK.EOF, self._line, self._line_pos + 1, '')
358
359            match = self._SIMPLE_TOKEN_MATCHER.match(
360                    self._line_buf, self._line_pos)
361            if not match:
362                return None
363            kind, start, end = match
364
365            # Skip comments and spaces
366            if ((kind == TK.SPACE and not self._line_start) or
367                (kind == TK.ESC_NEWLINE) or
368                (kind == TK.COMMENT)):
369                self._line_pos = end
370                continue
371
372            # Save the peaked token
373            token = Token(kind, self._line, self._line_pos + 1,
374                          self._line_buf[start:end])
375            self._next_token = token
376            self._next_pos = end
377            return token
378
379
380    def lex(self):
381        token = self.peek()
382        if not token:
383            self.raise_error()
384        self._line_start = token.kind == TK.NEWLINE
385        self._line_pos = self._next_pos
386        self._next_token = None
387        self._next_pos = None
388        return token
389
390
391    def lex_match(self, match_set):
392        token = self.lex()
393        if token.kind not in match_set:
394            self.raise_error()
395        return token
396
397
398    class STR_TK(object):
399        END = 0
400        CHARS = 1
401        ESC_CHAR = 2
402        ESC_NEWLINE = 3
403        VAR = 4
404        CURVE_VAR = 5
405
406
407    _PATH_TOKEN_MATCHER = TokenMatcher([
408        (STR_TK.END, r'[ \t\n|:]'),
409        (STR_TK.CHARS, r'[^ \t\n|:$]+'),
410        (STR_TK.ESC_CHAR, r'\$[^\n{\w_-]'),
411        (STR_TK.ESC_NEWLINE, r'\$\n[ \t]*'),
412        (STR_TK.VAR, r'\$[\w_-]+'),
413        (STR_TK.CURVE_VAR, r'\$\{[\w_.-]+\}'),
414    ])
415
416
417    _STR_TOKEN_MATCHER = TokenMatcher([
418        (STR_TK.END, r'\n+'),
419        (STR_TK.CHARS, r'[^\n$]+'),
420        (STR_TK.ESC_CHAR, r'\$[^\n{\w_-]'),
421        (STR_TK.ESC_NEWLINE, r'\$\n[ \t]*'),
422        (STR_TK.VAR, r'\$[\w_-]+'),
423        (STR_TK.CURVE_VAR, r'\$\{[\w_.-]+\}'),
424    ])
425
426
427    def _lex_string_or_path(self, matcher, result_kind):
428        self._ensure_line()
429        self._skip_space()
430
431        start_line = self._line
432        start_column = self._line_pos + 1
433
434        builder = EvalStringBuilder()
435
436        while True:
437            if not self._ensure_line():
438                break
439
440            match = matcher.match(self._line_buf, self._line_pos)
441            if not match:
442                self.raise_error('unknown character sequence')
443
444            kind, start, end = match
445            if kind == self.STR_TK.END:
446                break
447
448            self._line_pos = end
449
450            if kind == self.STR_TK.CHARS:
451                builder.append_raw(self._line_buf[start:end])
452            elif kind == self.STR_TK.ESC_CHAR:
453                ch = self._line_buf[start + 1]
454                if ch in ' \t:$':
455                    builder.append_raw(ch)
456                else:
457                    self.raise_error('bad escape sequence')
458            elif kind == self.STR_TK.ESC_NEWLINE:
459                if not self._read_next_line():
460                    break
461                self._skip_space()
462            elif kind == self.STR_TK.VAR:
463                builder.append_var(self._line_buf[start + 1 : end])
464            else:
465                assert kind == self.STR_TK.CURVE_VAR
466                builder.append_var(self._line_buf[start + 2 : end - 1])
467
468        self._next_token = None
469        return Token(result_kind, start_line, start_column, builder.getvalue())
470
471
472    def lex_path(self):
473        return self._lex_string_or_path(self._PATH_TOKEN_MATCHER, TK.PATH)
474
475
476    def lex_string(self):
477        return self._lex_string_or_path(self._STR_TOKEN_MATCHER, TK.STRING)
478
479
480Manifest = collections.namedtuple('Manifest', 'builds rules pools defaults')
481
482
483class Parser(object):
484    """Ninja Manifest Parser
485
486    This parser parses ninja-build manifest files, such as::
487
488        cflags = -Wall
489
490        pool cc_pool
491          depth = 1
492
493        rule cc
494          command = gcc -c -o $out $in $cflags $extra_cflags
495          pool = cc_pool
496
497        build test.o : cc test.c
498          extra_cflags = -Werror
499
500        default test.o
501
502    Example:
503        >>> manifest = Parser().parse('build.ninja', 'utf-8')
504        >>> print(manifest.builds)
505
506    """
507
508
509    def __init__(self, base_dir=None):
510        if base_dir is None:
511            self._base_dir = os.getcwd()
512        else:
513            self._base_dir = base_dir
514
515        # File context
516        self._context = []
517        self._lexer = None
518        self._env = None
519
520        # Intermediate results
521        self._builds = []
522        self._rules = []
523        self._pools = []
524        self._defaults = []
525
526        self._rules_dict = {}
527
528
529    def _push_context(self, lexer, env):
530        """Push a parsing file context.
531
532        Args:
533            lexer: Lexer for the associated file.
534            env: Environment for global variable bindings.
535        """
536
537        self._context.append((self._lexer, self._env))
538        self._lexer = lexer
539        self._env = env
540
541
542    def _pop_context(self):
543        """Push a parsing file context."""
544
545        current_context = (self._lexer, self._env)
546        self._lexer, self._env = self._context.pop()
547        return current_context
548
549
550    def parse(self, path, encoding, depfile=None):
551        """Parse a ninja-build manifest file.
552
553        Args:
554            path (str): Input file path to be parsed.
555            encoding (str): Input file encoding.
556
557        Returns:
558            Manifest: Parsed manifest for the given ninja-build manifest file.
559        """
560
561        self._parse_internal(path, encoding, EvalEnv())
562        if depfile:
563            self.parse_dep_file(depfile, encoding)
564        return Manifest(self._builds, self._rules, self._pools, self._defaults)
565
566
567    def _parse_internal(self, path, encoding, env):
568        path = os.path.join(self._base_dir, path)
569        with open(path, 'r', encoding=encoding) as fp:
570            self._push_context(Lexer(fp, path, encoding), env)
571            try:
572                self._parse_all_top_level_stmts()
573            finally:
574                self._pop_context()
575
576
577    def _parse_all_top_level_stmts(self):
578        """Parse all top-level statements in a file."""
579        while self._parse_top_level_stmt():
580            pass
581
582
583    def _parse_top_level_stmt(self):
584        """Parse a top level statement."""
585
586        token = self._lexer.peek()
587        if not token:
588            # An unexpected non-trivial token occurs.  Raise an error.
589            self._lexer.raise_error()
590
591        if token.kind == TK.EOF:
592            return False
593        elif token.kind == TK.NEWLINE:
594            self._lexer.lex()
595        elif token.kind == TK.IDENT:
596            ident = token.value
597            if ident == 'rule':
598                self._parse_rule_stmt()
599            elif ident == 'build':
600                self._parse_build_stmt()
601            elif ident == 'default':
602                self._parse_default_stmt()
603            elif ident == 'pool':
604                self._parse_pool_stmt()
605            elif ident in {'subninja', 'include'}:
606                self._parse_include_stmt()
607            else:
608                self._parse_global_binding_stmt()
609        else:
610            # An unexpected trivial token occurs.  Raise an error.
611            self._lexer.raise_error()
612        return True
613
614
615    def _parse_path_list(self, end_set):
616        """Parse a list of paths."""
617
618        result = []
619        while True:
620            token = self._lexer.peek()
621            if token:
622                if token.kind in end_set:
623                    break
624                elif token.kind != TK.IDENT:
625                    self._lexer.raise_error()
626
627            token = self._lexer.lex_path()
628            result.append(token.value)
629        return result
630
631
632    def _parse_binding_stmt(self):
633        """Parse a variable binding statement.
634
635        Example:
636            IDENT = STRING
637        """
638        key = self._lexer.lex_match({TK.IDENT}).value
639        self._lexer.lex_match({TK.ASSIGN})
640        token = self._lexer.lex_string()
641        value = token.value
642        self._lexer.lex_match({TK.NEWLINE, TK.EOF})
643        return (key, value)
644
645
646    def _parse_global_binding_stmt(self):
647        """Parse a global variable binding statement.
648
649        Example:
650            IDENT = STRING
651        """
652
653        key, value = self._parse_binding_stmt()
654        value = eval_string(value, self._env)
655        self._env[key] = value
656
657
658    def _parse_local_binding_block(self):
659        """Parse several local variable bindings.
660
661        Example:
662            SPACE IDENT1 = STRING1
663            SPACE IDENT2 = STRING2
664        """
665        result = EvalEnv()
666        while True:
667            token = self._lexer.peek()
668            if not token or token.kind != TK.SPACE:
669                break
670            self._lexer.lex()
671            key, value = self._parse_binding_stmt()
672            result[key] = value
673        return result
674
675
676    def _parse_build_stmt(self):
677        """Parse `build` statement.
678
679        Example:
680            build PATH1 PATH2 | PATH3 PATH4 : IDENT PATH5 PATH6 | $
681                  PATH7 PATH8 || PATH9 PATH10
682            SPACE IDENT1 = STRING1
683            SPACE IDENT2 = STRING2
684        """
685
686        token = self._lexer.lex_match({TK.IDENT})
687        assert token.value == 'build'
688
689        build = Build()
690
691        # Parse explicit outs
692        explicit_outs = self._parse_path_list({TK.PIPE, TK.COLON})
693
694        # Parse implicit outs
695        token = self._lexer.peek()
696        if token.kind == TK.PIPE:
697            self._lexer.lex()
698            implicit_outs = self._parse_path_list({TK.COLON})
699        else:
700            implicit_outs = tuple()
701
702        self._lexer.lex_match({TK.COLON})
703
704        # Parse rule name for this build statement
705        build.rule = self._lexer.lex_match({TK.IDENT}).value
706        try:
707            rule_env = self._rules_dict[build.rule].bindings
708        except KeyError:
709            if build.rule != 'phony':
710                self._lexer.raise_error('undeclared rule name')
711            rule_env = self._env
712
713        # Parse explicit ins
714        explicit_ins = self._parse_path_list(
715                {TK.PIPE, TK.PIPE2, TK.NEWLINE, TK.EOF})
716
717        # Parse implicit ins
718        token = self._lexer.peek()
719        if token.kind == TK.PIPE:
720            self._lexer.lex()
721            implicit_ins = self._parse_path_list({TK.PIPE2, TK.NEWLINE, TK.EOF})
722        else:
723            implicit_ins = tuple()
724
725        # Parse order-only prerequisites
726        token = self._lexer.peek()
727        if token.kind == TK.PIPE2:
728            self._lexer.lex()
729            prerequisites = self._parse_path_list({TK.NEWLINE, TK.EOF})
730        else:
731            prerequisites = tuple()
732
733        self._lexer.lex_match({TK.NEWLINE, TK.EOF})
734
735        # Parse local bindings
736        bindings = self._parse_local_binding_block()
737        bindings.parent = self._env
738        if bindings:
739            build.bindings = bindings
740        else:
741            # Don't keep the empty ``dict`` object if there are no bindings
742            build.bindings = None
743
744        # Evaluate all paths
745        env = BuildEvalEnv(bindings, rule_env)
746
747        build.explicit_outs = eval_path_strings(explicit_outs, env)
748        build.implicit_outs = eval_path_strings(implicit_outs, env)
749        build.explicit_ins = eval_path_strings(explicit_ins, env)
750        build.implicit_ins = eval_path_strings(implicit_ins, env)
751        build.prerequisites = eval_path_strings(prerequisites, env)
752        build.depfile_implicit_ins = tuple()
753
754        self._builds.append(build)
755
756
757    def _parse_rule_stmt(self):
758        """Parse a `rule` statement.
759
760        Example:
761            rule IDENT
762            SPACE IDENT1 = STRING1
763            SPACE IDENT2 = STRING2
764        """
765
766        token = self._lexer.lex_match({TK.IDENT})
767        assert token.value == 'rule'
768
769        rule = Rule()
770        rule.name = self._lexer.lex_match({TK.IDENT}).value
771        self._lexer.lex_match({TK.NEWLINE, TK.EOF})
772        rule.bindings = self._parse_local_binding_block()
773
774        self._rules.append(rule)
775        self._rules_dict[rule.name] = rule
776
777
778    def _parse_default_stmt(self):
779        """Parse a `default` statement.
780
781        Example:
782            default PATH1 PATH2 PATH3
783        """
784
785        token = self._lexer.lex_match({TK.IDENT})
786        assert token.value == 'default'
787
788        default = Default()
789        outs = self._parse_path_list({TK.NEWLINE, TK.EOF})
790        default.outs = eval_path_strings(outs, self._env)
791
792        self._lexer.lex_match({TK.NEWLINE, TK.EOF})
793
794        self._defaults.append(default)
795
796
797    def _parse_pool_stmt(self):
798        """Parse a `pool` statement.
799
800        Example:
801            pool IDENT
802            SPACE IDENT1 = STRING1
803            SPACE IDENT2 = STRING2
804        """
805        token = self._lexer.lex_match({TK.IDENT})
806        assert token.value == 'pool'
807
808        pool = Pool()
809
810        token = self._lexer.lex()
811        assert token.kind == TK.IDENT
812        pool.name = token.value
813
814        self._lexer.lex_match({TK.NEWLINE, TK.EOF})
815
816        pool.bindings = self._parse_local_binding_block()
817
818        self._pools.append(pool)
819
820
821    def _parse_include_stmt(self):
822        """Parse an `include` or `subninja` statement.
823
824        Example:
825            include PATH
826            subninja PATH
827        """
828
829        token = self._lexer.lex_match({TK.IDENT})
830        assert token.value in {'include', 'subninja'}
831        wrap_env = token.value == 'subninja'
832
833        token = self._lexer.lex_path()
834        path = eval_string(token.value, self._env)  # XXX: Check lookup order
835        self._lexer.lex_match({TK.NEWLINE, TK.EOF})
836
837        if wrap_env:
838            env = EvalEnv()
839            env.parent = self._env
840        else:
841            env = self._env
842        self._parse_internal(path, self._lexer.encoding, env)
843
844
845    def parse_dep_file(self, path, encoding):
846        depfile = DepFileParser().parse(path, encoding)
847        for build in self._builds:
848            depfile_implicit_ins = set()
849            for explicit_out in build.explicit_outs:
850                deps = depfile.get(explicit_out)
851                if deps:
852                    depfile_implicit_ins.update(deps.implicit_ins)
853            build.depfile_implicit_ins = tuple(sorted(depfile_implicit_ins))
854
855
856class DepFileError(ValueError):
857    pass
858
859
860class DepFileRecord(object):
861    __slots__ = ('id', 'explicit_out', 'mtime', 'implicit_ins')
862
863
864    def __init__(self, id, explicit_out, mtime, implicit_ins):
865        self.id = id
866        self.explicit_out = explicit_out
867        self.mtime = mtime
868        self.implicit_ins = implicit_ins
869
870
871class DepFileParser(object):
872    """Ninja deps log parser which parses ``.ninja_deps`` file.
873    """
874
875
876    def __init__(self):
877        self._deps = []
878        self._paths = []
879        self._path_deps = {}
880
881
882    def parse(self, path, encoding):
883        with open(path, 'rb') as fp:
884            return self._parse(fp, encoding)
885
886
887    @staticmethod
888    def _unpack_uint32(buf):
889        return struct.unpack('<I', buf)[0]
890
891
892    @staticmethod
893    def _unpack_uint32_iter(buf):
894        for p in struct.iter_unpack('<I', buf):
895            yield p[0]
896
897
898    if sys.version_info < (3,):
899        @staticmethod
900        def _extract_path(s, encoding):
901            pos = len(s)
902            count = 3
903            while count > 0 and pos > 0 and s[pos - 1] == b'\0':
904                pos -= 1
905                count -= 1
906            return intern(s[0:pos])
907    else:
908        @staticmethod
909        def _extract_path(s, encoding):
910            pos = len(s)
911            count = 3
912            while count > 0 and pos > 0 and s[pos - 1] == 0:
913                pos -= 1
914                count -= 1
915            return intern(s[0:pos].decode(encoding))
916
917
918    def _get_path(self, index):
919        try:
920            return self._paths[index]
921        except IndexError:
922            raise DepFileError('path index overflow')
923
924
925    def _parse(self, fp, encoding):
926        # Check the magic word
927        if fp.readline() != b'# ninjadeps\n':
928            raise DepFileError('bad magic word')
929
930        # Check the file format version
931        version = self._unpack_uint32(fp.read(4))
932        if version != 3:
933            raise DepFileError('unsupported deps log version: ' + str(version))
934
935        # Read the records
936        MAX_RECORD_SIZE = (1 << 19) - 1
937        while True:
938            buf = fp.read(4)
939            if not buf:
940                break
941
942            record_size = self._unpack_uint32(buf)
943            is_dep = bool(record_size >> 31)
944            record_size &= (1 << 31) - 1
945
946            if record_size > MAX_RECORD_SIZE:
947                raise DepFileError('record size overflow')
948
949            if is_dep:
950                if record_size % 4 != 0 or record_size < 8:
951                    raise DepFileError('corrupted deps record')
952
953                buf = fp.read(record_size)
954
955                dep_iter = self._unpack_uint32_iter(buf)
956
957                idx = len(self._deps)
958                explicit_out = self._get_path(next(dep_iter))
959                mtime = next(dep_iter)
960                implicit_ins = [self._get_path(p) for p in dep_iter]
961
962                deps = DepFileRecord(idx, explicit_out, mtime, implicit_ins)
963
964                old_deps = self._path_deps.get(explicit_out)
965                if not old_deps:
966                    self._deps.append(deps)
967                    self._path_deps[explicit_out] = deps
968                elif old_deps.mtime > deps.mtime:
969                    self._deps.append(None)
970                else:
971                    self._deps[old_deps.id] = None
972                    self._deps.append(deps)
973                    self._path_deps[explicit_out] = deps
974            else:
975                if record_size < 4:
976                    raise DepFileError('corrupted path record')
977                buf = fp.read(record_size - 4)
978                path = self._extract_path(buf, encoding)
979                buf = fp.read(4)
980                checksum = 0xffffffff ^ self._unpack_uint32(buf)
981                if len(self._paths) != checksum:
982                    raise DepFileError('bad path record checksum')
983                self._paths.append(path)
984
985        return self._path_deps
986
987
988def main():
989    # Parse command line options
990    parser = argparse.ArgumentParser()
991    parser.add_argument('input_file', help='input ninja file')
992    parser.add_argument('--encoding', default='utf-8',
993                        help='ninja file encoding')
994    parser.add_argument('--ninja-deps', help='.ninja_deps file')
995    args = parser.parse_args()
996
997    if DEBUG_ALLOC:
998        tracemalloc.start(25)
999        tc_start = tracemalloc.take_snapshot()
1000
1001    # Parse ninja file
1002    manifest = Parser().parse(args.input_file, args.encoding, args.ninja_deps)
1003
1004    if DEBUG_ALLOC:
1005        tc_end = tracemalloc.take_snapshot()
1006
1007    for rule in manifest.rules:
1008        print('rule', rule.name)
1009
1010    for build in manifest.builds:
1011        print('build')
1012        for path in build.explicit_outs:
1013            print('  explicit_out:', path)
1014        for path in build.implicit_outs:
1015            print('  implicit_out:', path)
1016        for path in build.explicit_ins:
1017            print('  explicit_in:', path)
1018        for path in build.implicit_ins:
1019            print('  implicit_in:', path)
1020        for path in build.prerequisites:
1021            print('  prerequisites:', path)
1022        for path in build.depfile_implicit_ins:
1023            print('  depfile_implicit_in:', path)
1024
1025    for pool in manifest.pools:
1026        print('pool', pool.name)
1027
1028    for default in manifest.defaults:
1029        print('default')
1030        for path in default.outs:
1031            print('  out:', path)
1032
1033    if DEBUG_ALLOC:
1034        top_stats = tc_end.compare_to(tc_start, 'traceback')
1035        with open('tracemalloc.log', 'w') as fp:
1036            for s in top_stats:
1037                print('', file=fp)
1038                print('========================================', file=fp)
1039                print(s, file=fp)
1040                for line in s.traceback.format():
1041                    print(line, file=fp)
1042
1043if __name__ == '__main__':
1044    main()
1045