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