#!/usr/bin/env python3 # Copyright (c) Meta Platforms, Inc. and affiliates. # All rights reserved. # # This source code is licensed under the BSD-style license found in the # LICENSE file in the root directory of this source tree. """Prints headers that are visible to executorch clients.""" import json import os import subprocess from dataclasses import dataclass from typing import Dict, List # Run buck2 from the same directory (and thus repo) as this script. BUCK_CWD: str = os.path.dirname(os.path.realpath(__file__)) # One of the non-executorch entries of clients.bzl EXTERNAL_CLIENT_TARGET: str = "fbcode//pye/model_inventory/..." # The buck query covering the targets to examine. PROJECT_QUERY: str = "//executorch/..." @dataclass class BuildTarget: """A buck build target and a subset of its attributes.""" name: str exported_deps: List[str] exported_headers: List[str] visibility: List[str] def query_targets(query: str) -> Dict[str, BuildTarget]: """Returns the BuildTargets matching the query, keyed by target name.""" args: List[str] = [ "buck2", "cquery", query, "--output-attribute", "exported_deps", "--output-attribute", "exported_headers", "--output-attribute", "visibility", ] cp: subprocess.CompletedProcess = subprocess.run( args, capture_output=True, cwd=BUCK_CWD, check=True ) # stdout should be a JSON object like P643366873. targets: dict = json.loads(cp.stdout) ret: Dict[str, BuildTarget] = {} for name, info in targets.items(): # Target strings may have an extra " (mode//config/string)" at the end. name = name.split(" ", 1)[0] exported_deps = [d.split(" ", 1)[0] for d in info.get("exported_deps", [])] ret[name] = BuildTarget( name=name, exported_deps=exported_deps, exported_headers=info.get("exported_headers", []), visibility=info.get("visibility", []), ) return ret def targets_exported_by( target: BuildTarget, targets: Dict[str, BuildTarget] ) -> List[BuildTarget]: """Returns the targets transitively exported by `target`.""" ret: List[BuildTarget] = [] for t in target.exported_deps: if t in targets: ret.append(targets[t]) # Recurse. Assumes there are no circular references, since buck # should fail if they exist. ret.extend(targets_exported_by(targets[t], targets)) return ret def find_visible_targets( client_target: str, targets: Dict[str, BuildTarget] ) -> List[BuildTarget]: """Returns a list of targets visible to client_target. Returned targets may be directly visible, or transitively visible via exported_deps. """ visible: List[BuildTarget] = [] for target in targets.values(): if client_target in target.visibility or "PUBLIC" in target.visibility: visible.append(target) visible.extend(targets_exported_by(target, targets)) return visible def index_headers(targets: List[BuildTarget]) -> Dict[str, List[BuildTarget]]: """Returns a mapping of header paths to the BuildTargets that export them.""" ret: Dict[str, List[BuildTarget]] = {} for target in targets: if isinstance(target.exported_headers, dict): # Dict of {"HeaderName.h": "fbcode//...[HeaderName.h] (mode//config)"} for header in target.exported_headers.values(): header = header.split(" ", 1)[0] if header not in ret: ret[header] = [] ret[header].append(target) else: # Simple list of header file paths, prefixed with "fbcode//". assert isinstance(target.exported_headers, list) for header in target.exported_headers: if header not in ret: ret[header] = [] ret[header].append(target) return ret def main(): all_targets = query_targets(PROJECT_QUERY) visible_targets = find_visible_targets(EXTERNAL_CLIENT_TARGET, all_targets) index = index_headers(visible_targets) # The list will be build targets like `fbcode//executorch/runtime/platform/platform.h`. print("\n".join(sorted(index.keys()))) if __name__ == "__main__": main()