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