1*523fa7a6SAndroid Build Coastguard Worker#!/usr/bin/env python3 2*523fa7a6SAndroid Build Coastguard Worker# Copyright (c) Meta Platforms, Inc. and affiliates. 3*523fa7a6SAndroid Build Coastguard Worker# All rights reserved. 4*523fa7a6SAndroid Build Coastguard Worker# 5*523fa7a6SAndroid Build Coastguard Worker# This source code is licensed under the BSD-style license found in the 6*523fa7a6SAndroid Build Coastguard Worker# LICENSE file in the root directory of this source tree. 7*523fa7a6SAndroid Build Coastguard Worker 8*523fa7a6SAndroid Build Coastguard Worker"""Prints headers that are visible to executorch clients.""" 9*523fa7a6SAndroid Build Coastguard Worker 10*523fa7a6SAndroid Build Coastguard Workerimport json 11*523fa7a6SAndroid Build Coastguard Workerimport os 12*523fa7a6SAndroid Build Coastguard Workerimport subprocess 13*523fa7a6SAndroid Build Coastguard Worker 14*523fa7a6SAndroid Build Coastguard Workerfrom dataclasses import dataclass 15*523fa7a6SAndroid Build Coastguard Workerfrom typing import Dict, List 16*523fa7a6SAndroid Build Coastguard Worker 17*523fa7a6SAndroid Build Coastguard Worker 18*523fa7a6SAndroid Build Coastguard Worker# Run buck2 from the same directory (and thus repo) as this script. 19*523fa7a6SAndroid Build Coastguard WorkerBUCK_CWD: str = os.path.dirname(os.path.realpath(__file__)) 20*523fa7a6SAndroid Build Coastguard Worker 21*523fa7a6SAndroid Build Coastguard Worker# One of the non-executorch entries of clients.bzl 22*523fa7a6SAndroid Build Coastguard WorkerEXTERNAL_CLIENT_TARGET: str = "fbcode//pye/model_inventory/..." 23*523fa7a6SAndroid Build Coastguard Worker 24*523fa7a6SAndroid Build Coastguard Worker# The buck query covering the targets to examine. 25*523fa7a6SAndroid Build Coastguard WorkerPROJECT_QUERY: str = "//executorch/..." 26*523fa7a6SAndroid Build Coastguard Worker 27*523fa7a6SAndroid Build Coastguard Worker 28*523fa7a6SAndroid Build Coastguard Worker@dataclass 29*523fa7a6SAndroid Build Coastguard Workerclass BuildTarget: 30*523fa7a6SAndroid Build Coastguard Worker """A buck build target and a subset of its attributes.""" 31*523fa7a6SAndroid Build Coastguard Worker 32*523fa7a6SAndroid Build Coastguard Worker name: str 33*523fa7a6SAndroid Build Coastguard Worker exported_deps: List[str] 34*523fa7a6SAndroid Build Coastguard Worker exported_headers: List[str] 35*523fa7a6SAndroid Build Coastguard Worker visibility: List[str] 36*523fa7a6SAndroid Build Coastguard Worker 37*523fa7a6SAndroid Build Coastguard Worker 38*523fa7a6SAndroid Build Coastguard Workerdef query_targets(query: str) -> Dict[str, BuildTarget]: 39*523fa7a6SAndroid Build Coastguard Worker """Returns the BuildTargets matching the query, keyed by target name.""" 40*523fa7a6SAndroid Build Coastguard Worker args: List[str] = [ 41*523fa7a6SAndroid Build Coastguard Worker "buck2", 42*523fa7a6SAndroid Build Coastguard Worker "cquery", 43*523fa7a6SAndroid Build Coastguard Worker query, 44*523fa7a6SAndroid Build Coastguard Worker "--output-attribute", 45*523fa7a6SAndroid Build Coastguard Worker "exported_deps", 46*523fa7a6SAndroid Build Coastguard Worker "--output-attribute", 47*523fa7a6SAndroid Build Coastguard Worker "exported_headers", 48*523fa7a6SAndroid Build Coastguard Worker "--output-attribute", 49*523fa7a6SAndroid Build Coastguard Worker "visibility", 50*523fa7a6SAndroid Build Coastguard Worker ] 51*523fa7a6SAndroid Build Coastguard Worker cp: subprocess.CompletedProcess = subprocess.run( 52*523fa7a6SAndroid Build Coastguard Worker args, capture_output=True, cwd=BUCK_CWD, check=True 53*523fa7a6SAndroid Build Coastguard Worker ) 54*523fa7a6SAndroid Build Coastguard Worker # stdout should be a JSON object like P643366873. 55*523fa7a6SAndroid Build Coastguard Worker targets: dict = json.loads(cp.stdout) 56*523fa7a6SAndroid Build Coastguard Worker 57*523fa7a6SAndroid Build Coastguard Worker ret: Dict[str, BuildTarget] = {} 58*523fa7a6SAndroid Build Coastguard Worker for name, info in targets.items(): 59*523fa7a6SAndroid Build Coastguard Worker # Target strings may have an extra " (mode//config/string)" at the end. 60*523fa7a6SAndroid Build Coastguard Worker name = name.split(" ", 1)[0] 61*523fa7a6SAndroid Build Coastguard Worker exported_deps = [d.split(" ", 1)[0] for d in info.get("exported_deps", [])] 62*523fa7a6SAndroid Build Coastguard Worker ret[name] = BuildTarget( 63*523fa7a6SAndroid Build Coastguard Worker name=name, 64*523fa7a6SAndroid Build Coastguard Worker exported_deps=exported_deps, 65*523fa7a6SAndroid Build Coastguard Worker exported_headers=info.get("exported_headers", []), 66*523fa7a6SAndroid Build Coastguard Worker visibility=info.get("visibility", []), 67*523fa7a6SAndroid Build Coastguard Worker ) 68*523fa7a6SAndroid Build Coastguard Worker return ret 69*523fa7a6SAndroid Build Coastguard Worker 70*523fa7a6SAndroid Build Coastguard Worker 71*523fa7a6SAndroid Build Coastguard Workerdef targets_exported_by( 72*523fa7a6SAndroid Build Coastguard Worker target: BuildTarget, targets: Dict[str, BuildTarget] 73*523fa7a6SAndroid Build Coastguard Worker) -> List[BuildTarget]: 74*523fa7a6SAndroid Build Coastguard Worker """Returns the targets transitively exported by `target`.""" 75*523fa7a6SAndroid Build Coastguard Worker ret: List[BuildTarget] = [] 76*523fa7a6SAndroid Build Coastguard Worker for t in target.exported_deps: 77*523fa7a6SAndroid Build Coastguard Worker if t in targets: 78*523fa7a6SAndroid Build Coastguard Worker ret.append(targets[t]) 79*523fa7a6SAndroid Build Coastguard Worker # Recurse. Assumes there are no circular references, since buck 80*523fa7a6SAndroid Build Coastguard Worker # should fail if they exist. 81*523fa7a6SAndroid Build Coastguard Worker ret.extend(targets_exported_by(targets[t], targets)) 82*523fa7a6SAndroid Build Coastguard Worker return ret 83*523fa7a6SAndroid Build Coastguard Worker 84*523fa7a6SAndroid Build Coastguard Worker 85*523fa7a6SAndroid Build Coastguard Workerdef find_visible_targets( 86*523fa7a6SAndroid Build Coastguard Worker client_target: str, targets: Dict[str, BuildTarget] 87*523fa7a6SAndroid Build Coastguard Worker) -> List[BuildTarget]: 88*523fa7a6SAndroid Build Coastguard Worker """Returns a list of targets visible to client_target. 89*523fa7a6SAndroid Build Coastguard Worker 90*523fa7a6SAndroid Build Coastguard Worker Returned targets may be directly visible, or transitively visible via 91*523fa7a6SAndroid Build Coastguard Worker exported_deps. 92*523fa7a6SAndroid Build Coastguard Worker """ 93*523fa7a6SAndroid Build Coastguard Worker visible: List[BuildTarget] = [] 94*523fa7a6SAndroid Build Coastguard Worker for target in targets.values(): 95*523fa7a6SAndroid Build Coastguard Worker if client_target in target.visibility or "PUBLIC" in target.visibility: 96*523fa7a6SAndroid Build Coastguard Worker visible.append(target) 97*523fa7a6SAndroid Build Coastguard Worker visible.extend(targets_exported_by(target, targets)) 98*523fa7a6SAndroid Build Coastguard Worker return visible 99*523fa7a6SAndroid Build Coastguard Worker 100*523fa7a6SAndroid Build Coastguard Worker 101*523fa7a6SAndroid Build Coastguard Workerdef index_headers(targets: List[BuildTarget]) -> Dict[str, List[BuildTarget]]: 102*523fa7a6SAndroid Build Coastguard Worker """Returns a mapping of header paths to the BuildTargets that export them.""" 103*523fa7a6SAndroid Build Coastguard Worker ret: Dict[str, List[BuildTarget]] = {} 104*523fa7a6SAndroid Build Coastguard Worker for target in targets: 105*523fa7a6SAndroid Build Coastguard Worker if isinstance(target.exported_headers, dict): 106*523fa7a6SAndroid Build Coastguard Worker # Dict of {"HeaderName.h": "fbcode//...[HeaderName.h] (mode//config)"} 107*523fa7a6SAndroid Build Coastguard Worker for header in target.exported_headers.values(): 108*523fa7a6SAndroid Build Coastguard Worker header = header.split(" ", 1)[0] 109*523fa7a6SAndroid Build Coastguard Worker if header not in ret: 110*523fa7a6SAndroid Build Coastguard Worker ret[header] = [] 111*523fa7a6SAndroid Build Coastguard Worker ret[header].append(target) 112*523fa7a6SAndroid Build Coastguard Worker else: 113*523fa7a6SAndroid Build Coastguard Worker # Simple list of header file paths, prefixed with "fbcode//". 114*523fa7a6SAndroid Build Coastguard Worker assert isinstance(target.exported_headers, list) 115*523fa7a6SAndroid Build Coastguard Worker for header in target.exported_headers: 116*523fa7a6SAndroid Build Coastguard Worker if header not in ret: 117*523fa7a6SAndroid Build Coastguard Worker ret[header] = [] 118*523fa7a6SAndroid Build Coastguard Worker ret[header].append(target) 119*523fa7a6SAndroid Build Coastguard Worker return ret 120*523fa7a6SAndroid Build Coastguard Worker 121*523fa7a6SAndroid Build Coastguard Worker 122*523fa7a6SAndroid Build Coastguard Workerdef main(): 123*523fa7a6SAndroid Build Coastguard Worker all_targets = query_targets(PROJECT_QUERY) 124*523fa7a6SAndroid Build Coastguard Worker visible_targets = find_visible_targets(EXTERNAL_CLIENT_TARGET, all_targets) 125*523fa7a6SAndroid Build Coastguard Worker index = index_headers(visible_targets) 126*523fa7a6SAndroid Build Coastguard Worker # The list will be build targets like `fbcode//executorch/runtime/platform/platform.h`. 127*523fa7a6SAndroid Build Coastguard Worker print("\n".join(sorted(index.keys()))) 128*523fa7a6SAndroid Build Coastguard Worker 129*523fa7a6SAndroid Build Coastguard Worker 130*523fa7a6SAndroid Build Coastguard Workerif __name__ == "__main__": 131*523fa7a6SAndroid Build Coastguard Worker main() 132