xref: /aosp_15_r20/external/executorch/build/print_public_headers.py (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
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