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