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