xref: /aosp_15_r20/external/angle/build/gn_ast/gn_ast.py (revision 8975f5c5ed3d1c378011245431ada316dfb6f244)
1*8975f5c5SAndroid Build Coastguard Worker# Lint as: python3
2*8975f5c5SAndroid Build Coastguard Worker# Copyright 2023 The Chromium Authors
3*8975f5c5SAndroid Build Coastguard Worker# Use of this source code is governed by a BSD-style license that can be
4*8975f5c5SAndroid Build Coastguard Worker# found in the LICENSE file.
5*8975f5c5SAndroid Build Coastguard Worker"""Helper script to use GN's JSON interface to make changes.
6*8975f5c5SAndroid Build Coastguard Worker
7*8975f5c5SAndroid Build Coastguard WorkerAST implementation details:
8*8975f5c5SAndroid Build Coastguard Worker  https://gn.googlesource.com/gn/+/refs/heads/main/src/gn/parse_tree.cc
9*8975f5c5SAndroid Build Coastguard Worker
10*8975f5c5SAndroid Build Coastguard WorkerTo dump an AST:
11*8975f5c5SAndroid Build Coastguard Worker  gn format --dump-tree=json BUILD.gn > foo.json
12*8975f5c5SAndroid Build Coastguard Worker"""
13*8975f5c5SAndroid Build Coastguard Worker
14*8975f5c5SAndroid Build Coastguard Workerfrom __future__ import annotations
15*8975f5c5SAndroid Build Coastguard Worker
16*8975f5c5SAndroid Build Coastguard Workerimport dataclasses
17*8975f5c5SAndroid Build Coastguard Workerimport functools
18*8975f5c5SAndroid Build Coastguard Workerimport json
19*8975f5c5SAndroid Build Coastguard Workerimport subprocess
20*8975f5c5SAndroid Build Coastguard Workerfrom typing import Callable, Dict, List, Optional, Tuple, TypeVar
21*8975f5c5SAndroid Build Coastguard Worker
22*8975f5c5SAndroid Build Coastguard WorkerNODE_CHILD = 'child'
23*8975f5c5SAndroid Build Coastguard WorkerNODE_TYPE = 'type'
24*8975f5c5SAndroid Build Coastguard WorkerNODE_VALUE = 'value'
25*8975f5c5SAndroid Build Coastguard Worker
26*8975f5c5SAndroid Build Coastguard Worker_T = TypeVar('_T')
27*8975f5c5SAndroid Build Coastguard Worker
28*8975f5c5SAndroid Build Coastguard Worker
29*8975f5c5SAndroid Build Coastguard Workerdef _create_location_node(begin_line=1):
30*8975f5c5SAndroid Build Coastguard Worker    return {
31*8975f5c5SAndroid Build Coastguard Worker        'begin_column': 1,
32*8975f5c5SAndroid Build Coastguard Worker        'begin_line': begin_line,
33*8975f5c5SAndroid Build Coastguard Worker        'end_column': 2,
34*8975f5c5SAndroid Build Coastguard Worker        'end_line': begin_line,
35*8975f5c5SAndroid Build Coastguard Worker    }
36*8975f5c5SAndroid Build Coastguard Worker
37*8975f5c5SAndroid Build Coastguard Worker
38*8975f5c5SAndroid Build Coastguard Workerdef _wrap(node: dict):
39*8975f5c5SAndroid Build Coastguard Worker    kind = node[NODE_TYPE]
40*8975f5c5SAndroid Build Coastguard Worker    if kind == 'LIST':
41*8975f5c5SAndroid Build Coastguard Worker        return StringList(node)
42*8975f5c5SAndroid Build Coastguard Worker    if kind == 'BLOCK':
43*8975f5c5SAndroid Build Coastguard Worker        return BlockWrapper(node)
44*8975f5c5SAndroid Build Coastguard Worker    return NodeWrapper(node)
45*8975f5c5SAndroid Build Coastguard Worker
46*8975f5c5SAndroid Build Coastguard Worker
47*8975f5c5SAndroid Build Coastguard Workerdef _unwrap(thing):
48*8975f5c5SAndroid Build Coastguard Worker    if isinstance(thing, NodeWrapper):
49*8975f5c5SAndroid Build Coastguard Worker        return thing.node
50*8975f5c5SAndroid Build Coastguard Worker    return thing
51*8975f5c5SAndroid Build Coastguard Worker
52*8975f5c5SAndroid Build Coastguard Worker
53*8975f5c5SAndroid Build Coastguard Workerdef _find_node(root_node: dict, target_node: dict):
54*8975f5c5SAndroid Build Coastguard Worker    def recurse(node: dict) -> Optional[Tuple[dict, int]]:
55*8975f5c5SAndroid Build Coastguard Worker        children = node.get(NODE_CHILD)
56*8975f5c5SAndroid Build Coastguard Worker        if children:
57*8975f5c5SAndroid Build Coastguard Worker            for i, child in enumerate(children):
58*8975f5c5SAndroid Build Coastguard Worker                if child is target_node:
59*8975f5c5SAndroid Build Coastguard Worker                    return node, i
60*8975f5c5SAndroid Build Coastguard Worker                ret = recurse(child)
61*8975f5c5SAndroid Build Coastguard Worker                if ret is not None:
62*8975f5c5SAndroid Build Coastguard Worker                    return ret
63*8975f5c5SAndroid Build Coastguard Worker        return None
64*8975f5c5SAndroid Build Coastguard Worker
65*8975f5c5SAndroid Build Coastguard Worker    ret = recurse(root_node)
66*8975f5c5SAndroid Build Coastguard Worker    if ret is None:
67*8975f5c5SAndroid Build Coastguard Worker        raise Exception(
68*8975f5c5SAndroid Build Coastguard Worker            f'Node not found: {target_node}\nLooked in: {root_node}')
69*8975f5c5SAndroid Build Coastguard Worker    return ret
70*8975f5c5SAndroid Build Coastguard Worker
71*8975f5c5SAndroid Build Coastguard Worker@dataclasses.dataclass
72*8975f5c5SAndroid Build Coastguard Workerclass NodeWrapper:
73*8975f5c5SAndroid Build Coastguard Worker    """Base class for all wrappers."""
74*8975f5c5SAndroid Build Coastguard Worker    node: dict
75*8975f5c5SAndroid Build Coastguard Worker
76*8975f5c5SAndroid Build Coastguard Worker    @property
77*8975f5c5SAndroid Build Coastguard Worker    def node_type(self) -> str:
78*8975f5c5SAndroid Build Coastguard Worker        return self.node[NODE_TYPE]
79*8975f5c5SAndroid Build Coastguard Worker
80*8975f5c5SAndroid Build Coastguard Worker    @property
81*8975f5c5SAndroid Build Coastguard Worker    def node_value(self) -> str:
82*8975f5c5SAndroid Build Coastguard Worker        return self.node[NODE_VALUE]
83*8975f5c5SAndroid Build Coastguard Worker
84*8975f5c5SAndroid Build Coastguard Worker    @property
85*8975f5c5SAndroid Build Coastguard Worker    def node_children(self) -> List[dict]:
86*8975f5c5SAndroid Build Coastguard Worker        return self.node[NODE_CHILD]
87*8975f5c5SAndroid Build Coastguard Worker
88*8975f5c5SAndroid Build Coastguard Worker    @functools.cached_property
89*8975f5c5SAndroid Build Coastguard Worker    def first_child(self):
90*8975f5c5SAndroid Build Coastguard Worker        return _wrap(self.node_children[0])
91*8975f5c5SAndroid Build Coastguard Worker
92*8975f5c5SAndroid Build Coastguard Worker    @functools.cached_property
93*8975f5c5SAndroid Build Coastguard Worker    def second_child(self):
94*8975f5c5SAndroid Build Coastguard Worker        return _wrap(self.node_children[1])
95*8975f5c5SAndroid Build Coastguard Worker
96*8975f5c5SAndroid Build Coastguard Worker    def is_list(self):
97*8975f5c5SAndroid Build Coastguard Worker        return self.node_type == 'LIST'
98*8975f5c5SAndroid Build Coastguard Worker
99*8975f5c5SAndroid Build Coastguard Worker    def is_identifier(self):
100*8975f5c5SAndroid Build Coastguard Worker        return self.node_type == 'IDENTIFIER'
101*8975f5c5SAndroid Build Coastguard Worker
102*8975f5c5SAndroid Build Coastguard Worker    def visit_nodes(self, callback: Callable[[dict],
103*8975f5c5SAndroid Build Coastguard Worker                                             Optional[_T]]) -> List[_T]:
104*8975f5c5SAndroid Build Coastguard Worker        ret = []
105*8975f5c5SAndroid Build Coastguard Worker
106*8975f5c5SAndroid Build Coastguard Worker        def recurse(root: dict):
107*8975f5c5SAndroid Build Coastguard Worker            value = callback(root)
108*8975f5c5SAndroid Build Coastguard Worker            if value is not None:
109*8975f5c5SAndroid Build Coastguard Worker                ret.append(value)
110*8975f5c5SAndroid Build Coastguard Worker                return
111*8975f5c5SAndroid Build Coastguard Worker            children = root.get(NODE_CHILD)
112*8975f5c5SAndroid Build Coastguard Worker            if children:
113*8975f5c5SAndroid Build Coastguard Worker                for child in children:
114*8975f5c5SAndroid Build Coastguard Worker                    recurse(child)
115*8975f5c5SAndroid Build Coastguard Worker
116*8975f5c5SAndroid Build Coastguard Worker        recurse(self.node)
117*8975f5c5SAndroid Build Coastguard Worker        return ret
118*8975f5c5SAndroid Build Coastguard Worker
119*8975f5c5SAndroid Build Coastguard Worker    def set_location_recursive(self, line):
120*8975f5c5SAndroid Build Coastguard Worker        def helper(n: dict):
121*8975f5c5SAndroid Build Coastguard Worker            loc = n.get('location')
122*8975f5c5SAndroid Build Coastguard Worker            if loc:
123*8975f5c5SAndroid Build Coastguard Worker                loc['begin_line'] = line
124*8975f5c5SAndroid Build Coastguard Worker                loc['end_line'] = line
125*8975f5c5SAndroid Build Coastguard Worker
126*8975f5c5SAndroid Build Coastguard Worker        self.visit_nodes(helper)
127*8975f5c5SAndroid Build Coastguard Worker
128*8975f5c5SAndroid Build Coastguard Worker    def add_child(self, node, *, before=None):
129*8975f5c5SAndroid Build Coastguard Worker        node = _unwrap(node)
130*8975f5c5SAndroid Build Coastguard Worker        if before is None:
131*8975f5c5SAndroid Build Coastguard Worker            self.node_children.append(node)
132*8975f5c5SAndroid Build Coastguard Worker        else:
133*8975f5c5SAndroid Build Coastguard Worker            before = _unwrap(before)
134*8975f5c5SAndroid Build Coastguard Worker            parent_node, child_idx = _find_node(self.node, before)
135*8975f5c5SAndroid Build Coastguard Worker            parent_node[NODE_CHILD].insert(child_idx, node)
136*8975f5c5SAndroid Build Coastguard Worker
137*8975f5c5SAndroid Build Coastguard Worker            # Prevent blank lines between |before| and |node|.
138*8975f5c5SAndroid Build Coastguard Worker            target_line = before['location']['begin_line']
139*8975f5c5SAndroid Build Coastguard Worker            _wrap(node).set_location_recursive(target_line)
140*8975f5c5SAndroid Build Coastguard Worker
141*8975f5c5SAndroid Build Coastguard Worker    def remove_child(self, node):
142*8975f5c5SAndroid Build Coastguard Worker        node = _unwrap(node)
143*8975f5c5SAndroid Build Coastguard Worker        parent_node, child_idx = _find_node(self.node, node)
144*8975f5c5SAndroid Build Coastguard Worker        parent_node[NODE_CHILD].pop(child_idx)
145*8975f5c5SAndroid Build Coastguard Worker
146*8975f5c5SAndroid Build Coastguard Worker
147*8975f5c5SAndroid Build Coastguard Worker@dataclasses.dataclass
148*8975f5c5SAndroid Build Coastguard Workerclass BlockWrapper(NodeWrapper):
149*8975f5c5SAndroid Build Coastguard Worker    """Wraps a BLOCK node."""
150*8975f5c5SAndroid Build Coastguard Worker    def __post_init__(self):
151*8975f5c5SAndroid Build Coastguard Worker        assert self.node_type == 'BLOCK'
152*8975f5c5SAndroid Build Coastguard Worker
153*8975f5c5SAndroid Build Coastguard Worker    def find_assignments(self, var_name=None):
154*8975f5c5SAndroid Build Coastguard Worker        def match_fn(node: dict):
155*8975f5c5SAndroid Build Coastguard Worker            assignment = AssignmentWrapper.from_node(node)
156*8975f5c5SAndroid Build Coastguard Worker            if not assignment:
157*8975f5c5SAndroid Build Coastguard Worker                return None
158*8975f5c5SAndroid Build Coastguard Worker            if var_name is None or var_name == assignment.variable_name:
159*8975f5c5SAndroid Build Coastguard Worker                return assignment
160*8975f5c5SAndroid Build Coastguard Worker            return None
161*8975f5c5SAndroid Build Coastguard Worker
162*8975f5c5SAndroid Build Coastguard Worker        return self.visit_nodes(match_fn)
163*8975f5c5SAndroid Build Coastguard Worker
164*8975f5c5SAndroid Build Coastguard Worker
165*8975f5c5SAndroid Build Coastguard Worker@dataclasses.dataclass
166*8975f5c5SAndroid Build Coastguard Workerclass AssignmentWrapper(NodeWrapper):
167*8975f5c5SAndroid Build Coastguard Worker    """Wraps a =, +=, or -= BINARY node where the LHS is an identifier."""
168*8975f5c5SAndroid Build Coastguard Worker    def __post_init__(self):
169*8975f5c5SAndroid Build Coastguard Worker        assert self.node_type == 'BINARY'
170*8975f5c5SAndroid Build Coastguard Worker
171*8975f5c5SAndroid Build Coastguard Worker    @property
172*8975f5c5SAndroid Build Coastguard Worker    def variable_name(self):
173*8975f5c5SAndroid Build Coastguard Worker        return self.first_child.node_value
174*8975f5c5SAndroid Build Coastguard Worker
175*8975f5c5SAndroid Build Coastguard Worker    @property
176*8975f5c5SAndroid Build Coastguard Worker    def value(self):
177*8975f5c5SAndroid Build Coastguard Worker        return self.second_child
178*8975f5c5SAndroid Build Coastguard Worker
179*8975f5c5SAndroid Build Coastguard Worker    @property
180*8975f5c5SAndroid Build Coastguard Worker    def list_value(self):
181*8975f5c5SAndroid Build Coastguard Worker        ret = self.second_child
182*8975f5c5SAndroid Build Coastguard Worker        assert isinstance(ret, StringList), 'Found: ' + ret.node_type
183*8975f5c5SAndroid Build Coastguard Worker        return ret
184*8975f5c5SAndroid Build Coastguard Worker
185*8975f5c5SAndroid Build Coastguard Worker    @property
186*8975f5c5SAndroid Build Coastguard Worker    def operation(self):
187*8975f5c5SAndroid Build Coastguard Worker        """The assignment operation. Either "=" or "+="."""
188*8975f5c5SAndroid Build Coastguard Worker        return self.node_value
189*8975f5c5SAndroid Build Coastguard Worker
190*8975f5c5SAndroid Build Coastguard Worker    @property
191*8975f5c5SAndroid Build Coastguard Worker    def is_append(self):
192*8975f5c5SAndroid Build Coastguard Worker        return self.operation == '+='
193*8975f5c5SAndroid Build Coastguard Worker
194*8975f5c5SAndroid Build Coastguard Worker    def value_as_string_list(self):
195*8975f5c5SAndroid Build Coastguard Worker        return StringList(self.value.node)
196*8975f5c5SAndroid Build Coastguard Worker
197*8975f5c5SAndroid Build Coastguard Worker    @staticmethod
198*8975f5c5SAndroid Build Coastguard Worker    def from_node(node: dict) -> Optional[AssignmentWrapper]:
199*8975f5c5SAndroid Build Coastguard Worker        if node.get(NODE_TYPE) != 'BINARY':
200*8975f5c5SAndroid Build Coastguard Worker            return None
201*8975f5c5SAndroid Build Coastguard Worker        children = node[NODE_CHILD]
202*8975f5c5SAndroid Build Coastguard Worker        assert len(children) == 2, (
203*8975f5c5SAndroid Build Coastguard Worker            'Binary nodes should have two child nodes, but the node is: '
204*8975f5c5SAndroid Build Coastguard Worker            f'{node}')
205*8975f5c5SAndroid Build Coastguard Worker        left_child, right_child = children
206*8975f5c5SAndroid Build Coastguard Worker        if left_child.get(NODE_TYPE) != 'IDENTIFIER':
207*8975f5c5SAndroid Build Coastguard Worker            return None
208*8975f5c5SAndroid Build Coastguard Worker        if node.get(NODE_VALUE) not in ('=', '+=', '-='):
209*8975f5c5SAndroid Build Coastguard Worker            return None
210*8975f5c5SAndroid Build Coastguard Worker        return AssignmentWrapper(node)
211*8975f5c5SAndroid Build Coastguard Worker
212*8975f5c5SAndroid Build Coastguard Worker    @staticmethod
213*8975f5c5SAndroid Build Coastguard Worker    def create(variable_name, value, operation='='):
214*8975f5c5SAndroid Build Coastguard Worker        value_node = _unwrap(value)
215*8975f5c5SAndroid Build Coastguard Worker        id_node = {
216*8975f5c5SAndroid Build Coastguard Worker            'location': _create_location_node(),
217*8975f5c5SAndroid Build Coastguard Worker            'type': 'IDENTIFIER',
218*8975f5c5SAndroid Build Coastguard Worker            'value': variable_name,
219*8975f5c5SAndroid Build Coastguard Worker        }
220*8975f5c5SAndroid Build Coastguard Worker        return AssignmentWrapper({
221*8975f5c5SAndroid Build Coastguard Worker            'location': _create_location_node(),
222*8975f5c5SAndroid Build Coastguard Worker            'child': [id_node, value_node],
223*8975f5c5SAndroid Build Coastguard Worker            'type': 'BINARY',
224*8975f5c5SAndroid Build Coastguard Worker            'value': operation,
225*8975f5c5SAndroid Build Coastguard Worker        })
226*8975f5c5SAndroid Build Coastguard Worker
227*8975f5c5SAndroid Build Coastguard Worker    @staticmethod
228*8975f5c5SAndroid Build Coastguard Worker    def create_list(variable_name, operation='='):
229*8975f5c5SAndroid Build Coastguard Worker        return AssignmentWrapper.create(variable_name,
230*8975f5c5SAndroid Build Coastguard Worker                                        StringList.create(),
231*8975f5c5SAndroid Build Coastguard Worker                                        operation=operation)
232*8975f5c5SAndroid Build Coastguard Worker
233*8975f5c5SAndroid Build Coastguard Worker
234*8975f5c5SAndroid Build Coastguard Worker@dataclasses.dataclass
235*8975f5c5SAndroid Build Coastguard Workerclass StringList(NodeWrapper):
236*8975f5c5SAndroid Build Coastguard Worker    """Wraps a list node that contains only string literals."""
237*8975f5c5SAndroid Build Coastguard Worker    def __post_init__(self):
238*8975f5c5SAndroid Build Coastguard Worker        assert self.is_list()
239*8975f5c5SAndroid Build Coastguard Worker
240*8975f5c5SAndroid Build Coastguard Worker        self.literals: List[str] = [
241*8975f5c5SAndroid Build Coastguard Worker            x[NODE_VALUE].strip('"') for x in self.node_children
242*8975f5c5SAndroid Build Coastguard Worker            if x[NODE_TYPE] == 'LITERAL'
243*8975f5c5SAndroid Build Coastguard Worker        ]
244*8975f5c5SAndroid Build Coastguard Worker
245*8975f5c5SAndroid Build Coastguard Worker    def add_literal(self, value: str):
246*8975f5c5SAndroid Build Coastguard Worker        # For lists of deps, gn format will sort entries, but it will not
247*8975f5c5SAndroid Build Coastguard Worker        # move entries past comment boundaries. Insert at the front by default
248*8975f5c5SAndroid Build Coastguard Worker        # so that if sorting moves the value, and there is a comment boundary,
249*8975f5c5SAndroid Build Coastguard Worker        # it will end up before the comment instead of immediately after the
250*8975f5c5SAndroid Build Coastguard Worker        # comment (which likely does not apply to it).
251*8975f5c5SAndroid Build Coastguard Worker        self.literals.insert(0, value)
252*8975f5c5SAndroid Build Coastguard Worker        self.node_children.insert(
253*8975f5c5SAndroid Build Coastguard Worker            0, {
254*8975f5c5SAndroid Build Coastguard Worker                'location': _create_location_node(),
255*8975f5c5SAndroid Build Coastguard Worker                'type': 'LITERAL',
256*8975f5c5SAndroid Build Coastguard Worker                'value': f'"{value}"',
257*8975f5c5SAndroid Build Coastguard Worker            })
258*8975f5c5SAndroid Build Coastguard Worker
259*8975f5c5SAndroid Build Coastguard Worker    def remove_literal(self, value: str):
260*8975f5c5SAndroid Build Coastguard Worker        self.literals.remove(value)
261*8975f5c5SAndroid Build Coastguard Worker        quoted = f'"{value}"'
262*8975f5c5SAndroid Build Coastguard Worker        children = self.node_children
263*8975f5c5SAndroid Build Coastguard Worker        for i, node in enumerate(children):
264*8975f5c5SAndroid Build Coastguard Worker            if node[NODE_VALUE] == quoted:
265*8975f5c5SAndroid Build Coastguard Worker                children.pop(i)
266*8975f5c5SAndroid Build Coastguard Worker                break
267*8975f5c5SAndroid Build Coastguard Worker        else:
268*8975f5c5SAndroid Build Coastguard Worker            raise ValueError(f'Did not find child with value {quoted}')
269*8975f5c5SAndroid Build Coastguard Worker
270*8975f5c5SAndroid Build Coastguard Worker    @staticmethod
271*8975f5c5SAndroid Build Coastguard Worker    def create() -> StringList:
272*8975f5c5SAndroid Build Coastguard Worker        return StringList({
273*8975f5c5SAndroid Build Coastguard Worker            'location': _create_location_node(),
274*8975f5c5SAndroid Build Coastguard Worker            'begin_token': '[',
275*8975f5c5SAndroid Build Coastguard Worker            'child': [],
276*8975f5c5SAndroid Build Coastguard Worker            'end': {
277*8975f5c5SAndroid Build Coastguard Worker                'location': _create_location_node(),
278*8975f5c5SAndroid Build Coastguard Worker                'type': 'END',
279*8975f5c5SAndroid Build Coastguard Worker                'value': ']'
280*8975f5c5SAndroid Build Coastguard Worker            },
281*8975f5c5SAndroid Build Coastguard Worker            'type': 'LIST',
282*8975f5c5SAndroid Build Coastguard Worker        })
283*8975f5c5SAndroid Build Coastguard Worker
284*8975f5c5SAndroid Build Coastguard Worker
285*8975f5c5SAndroid Build Coastguard Workerclass Target(NodeWrapper):
286*8975f5c5SAndroid Build Coastguard Worker    """Wraps a target node.
287*8975f5c5SAndroid Build Coastguard Worker
288*8975f5c5SAndroid Build Coastguard Worker    A target node is any function besides "template" with exactly two children:
289*8975f5c5SAndroid Build Coastguard Worker      * Child 1: LIST with single string literal child
290*8975f5c5SAndroid Build Coastguard Worker      * Child 2: BLOCK
291*8975f5c5SAndroid Build Coastguard Worker
292*8975f5c5SAndroid Build Coastguard Worker    This does not actually find all targets. E.g. ignores those that use an
293*8975f5c5SAndroid Build Coastguard Worker    expression for a name, or that use "target(type, name)".
294*8975f5c5SAndroid Build Coastguard Worker    """
295*8975f5c5SAndroid Build Coastguard Worker    def __init__(self, function_node: dict, name_node: dict):
296*8975f5c5SAndroid Build Coastguard Worker        super().__init__(function_node)
297*8975f5c5SAndroid Build Coastguard Worker        self.name_node = name_node
298*8975f5c5SAndroid Build Coastguard Worker
299*8975f5c5SAndroid Build Coastguard Worker    @property
300*8975f5c5SAndroid Build Coastguard Worker    def name(self) -> str:
301*8975f5c5SAndroid Build Coastguard Worker        return self.name_node[NODE_VALUE].strip('"')
302*8975f5c5SAndroid Build Coastguard Worker
303*8975f5c5SAndroid Build Coastguard Worker    # E.g. "android_library"
304*8975f5c5SAndroid Build Coastguard Worker    @property
305*8975f5c5SAndroid Build Coastguard Worker    def type(self) -> str:
306*8975f5c5SAndroid Build Coastguard Worker        return self.node[NODE_VALUE]
307*8975f5c5SAndroid Build Coastguard Worker
308*8975f5c5SAndroid Build Coastguard Worker    @property
309*8975f5c5SAndroid Build Coastguard Worker    def block(self) -> BlockWrapper:
310*8975f5c5SAndroid Build Coastguard Worker        block = self.second_child
311*8975f5c5SAndroid Build Coastguard Worker        assert isinstance(block, BlockWrapper)
312*8975f5c5SAndroid Build Coastguard Worker        return block
313*8975f5c5SAndroid Build Coastguard Worker
314*8975f5c5SAndroid Build Coastguard Worker    def set_name(self, value):
315*8975f5c5SAndroid Build Coastguard Worker        self.name_node[NODE_VALUE] = f'"{value}"'
316*8975f5c5SAndroid Build Coastguard Worker
317*8975f5c5SAndroid Build Coastguard Worker    @staticmethod
318*8975f5c5SAndroid Build Coastguard Worker    def from_node(node: dict) -> Optional[Target]:
319*8975f5c5SAndroid Build Coastguard Worker        """Returns a Target if |node| is a target, None otherwise."""
320*8975f5c5SAndroid Build Coastguard Worker        if node.get(NODE_TYPE) != 'FUNCTION':
321*8975f5c5SAndroid Build Coastguard Worker            return None
322*8975f5c5SAndroid Build Coastguard Worker        if node.get(NODE_VALUE) == 'template':
323*8975f5c5SAndroid Build Coastguard Worker            return None
324*8975f5c5SAndroid Build Coastguard Worker        children = node.get(NODE_CHILD)
325*8975f5c5SAndroid Build Coastguard Worker        if not children or len(children) != 2:
326*8975f5c5SAndroid Build Coastguard Worker            return None
327*8975f5c5SAndroid Build Coastguard Worker        func_params_node, block_node = children
328*8975f5c5SAndroid Build Coastguard Worker        if block_node.get(NODE_TYPE) != 'BLOCK':
329*8975f5c5SAndroid Build Coastguard Worker            return None
330*8975f5c5SAndroid Build Coastguard Worker        if func_params_node.get(NODE_TYPE) != 'LIST':
331*8975f5c5SAndroid Build Coastguard Worker            return None
332*8975f5c5SAndroid Build Coastguard Worker        param_nodes = func_params_node.get(NODE_CHILD)
333*8975f5c5SAndroid Build Coastguard Worker        if param_nodes is None or len(param_nodes) != 1:
334*8975f5c5SAndroid Build Coastguard Worker            return None
335*8975f5c5SAndroid Build Coastguard Worker        name_node = param_nodes[0]
336*8975f5c5SAndroid Build Coastguard Worker        if name_node.get(NODE_TYPE) != 'LITERAL':
337*8975f5c5SAndroid Build Coastguard Worker            return None
338*8975f5c5SAndroid Build Coastguard Worker        return Target(function_node=node, name_node=name_node)
339*8975f5c5SAndroid Build Coastguard Worker
340*8975f5c5SAndroid Build Coastguard Worker
341*8975f5c5SAndroid Build Coastguard Workerclass BuildFile:
342*8975f5c5SAndroid Build Coastguard Worker    """Represents the contents of a BUILD.gn file."""
343*8975f5c5SAndroid Build Coastguard Worker    def __init__(self, path: str, root_node: dict):
344*8975f5c5SAndroid Build Coastguard Worker        self.block = BlockWrapper(root_node)
345*8975f5c5SAndroid Build Coastguard Worker        self.path = path
346*8975f5c5SAndroid Build Coastguard Worker        self._original_content = json.dumps(root_node)
347*8975f5c5SAndroid Build Coastguard Worker
348*8975f5c5SAndroid Build Coastguard Worker    def write_changes(self) -> bool:
349*8975f5c5SAndroid Build Coastguard Worker        """Returns whether there were any changes."""
350*8975f5c5SAndroid Build Coastguard Worker        new_content = json.dumps(self.block.node)
351*8975f5c5SAndroid Build Coastguard Worker        if new_content == self._original_content:
352*8975f5c5SAndroid Build Coastguard Worker            return False
353*8975f5c5SAndroid Build Coastguard Worker        output = subprocess.check_output(
354*8975f5c5SAndroid Build Coastguard Worker            ['gn', 'format', '--read-tree=json', self.path],
355*8975f5c5SAndroid Build Coastguard Worker            text=True,
356*8975f5c5SAndroid Build Coastguard Worker            input=new_content)
357*8975f5c5SAndroid Build Coastguard Worker        if 'Wrote rebuilt from json to' not in output:
358*8975f5c5SAndroid Build Coastguard Worker            raise Exception('JSON was invalid')
359*8975f5c5SAndroid Build Coastguard Worker        return True
360*8975f5c5SAndroid Build Coastguard Worker
361*8975f5c5SAndroid Build Coastguard Worker    @functools.cached_property
362*8975f5c5SAndroid Build Coastguard Worker    def targets(self) -> List[Target]:
363*8975f5c5SAndroid Build Coastguard Worker        return self.block.visit_nodes(Target.from_node)
364*8975f5c5SAndroid Build Coastguard Worker
365*8975f5c5SAndroid Build Coastguard Worker    @functools.cached_property
366*8975f5c5SAndroid Build Coastguard Worker    def targets_by_name(self) -> Dict[str, Target]:
367*8975f5c5SAndroid Build Coastguard Worker        return {t.name: t for t in self.targets}
368*8975f5c5SAndroid Build Coastguard Worker
369*8975f5c5SAndroid Build Coastguard Worker    @staticmethod
370*8975f5c5SAndroid Build Coastguard Worker    def from_file(path):
371*8975f5c5SAndroid Build Coastguard Worker        output = subprocess.check_output(
372*8975f5c5SAndroid Build Coastguard Worker            ['gn', 'format', '--dump-tree=json', path], text=True)
373*8975f5c5SAndroid Build Coastguard Worker        return BuildFile(path, json.loads(output))
374