xref: /aosp_15_r20/external/executorch/build/extract_sources.py (revision 523fa7a60841cd1ecfb9cc4201f1ca8b03ed023a)
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 Workerimport argparse
9*523fa7a6SAndroid Build Coastguard Workerimport copy
10*523fa7a6SAndroid Build Coastguard Workerimport os
11*523fa7a6SAndroid Build Coastguard Workerimport re
12*523fa7a6SAndroid Build Coastguard Worker
13*523fa7a6SAndroid Build Coastguard Workerfrom enum import Enum
14*523fa7a6SAndroid Build Coastguard Workerfrom typing import Any, List, Optional, Sequence
15*523fa7a6SAndroid Build Coastguard Worker
16*523fa7a6SAndroid Build Coastguard Workerfrom buck_util import Buck2Runner
17*523fa7a6SAndroid Build Coastguard Worker
18*523fa7a6SAndroid Build Coastguard Workertry:
19*523fa7a6SAndroid Build Coastguard Worker    import tomllib  # Standard in 3.11 and later
20*523fa7a6SAndroid Build Coastguard Workerexcept ModuleNotFoundError:
21*523fa7a6SAndroid Build Coastguard Worker    import tomli as tomllib
22*523fa7a6SAndroid Build Coastguard Worker
23*523fa7a6SAndroid Build Coastguard Worker"""Extracts source lists from the buck2 build system and writes them to a file.
24*523fa7a6SAndroid Build Coastguard Worker
25*523fa7a6SAndroid Build Coastguard WorkerThe config file is in TOML format and should contains one or more
26*523fa7a6SAndroid Build Coastguard Worker`[targets.<target-name>]` entries, along with an optional `[target_base]` entry.
27*523fa7a6SAndroid Build Coastguard Worker
28*523fa7a6SAndroid Build Coastguard WorkerAll of these may have the following lists of strings:
29*523fa7a6SAndroid Build Coastguard Worker- buck_targets: The list of buck targets that map to `<target-name>`.
30*523fa7a6SAndroid Build Coastguard Worker- deps: A list of other `<target-name>` entries that this target depends on.
31*523fa7a6SAndroid Build Coastguard Worker  Used to prune sources that are provided by those other targets.
32*523fa7a6SAndroid Build Coastguard Worker- filters: A list of regular expressions. This tool will only emit source files
33*523fa7a6SAndroid Build Coastguard Worker  whose relative paths match all entries.
34*523fa7a6SAndroid Build Coastguard Worker- excludes: A list of regular expressions. This tool will not emit source files
35*523fa7a6SAndroid Build Coastguard Worker  whose relative paths match any entry.
36*523fa7a6SAndroid Build Coastguard Worker
37*523fa7a6SAndroid Build Coastguard WorkerThe special `[target_base]` entry provides default lists that are inherited by
38*523fa7a6SAndroid Build Coastguard Workerthe `[target.<target-name>]` entries. When the `[target.<target-name>]` entry defines
39*523fa7a6SAndroid Build Coastguard Workera key that is already present in `[target_base]`, the target-specific entries are
40*523fa7a6SAndroid Build Coastguard Workerappended to the base list.
41*523fa7a6SAndroid Build Coastguard Worker
42*523fa7a6SAndroid Build Coastguard WorkerExample config:
43*523fa7a6SAndroid Build Coastguard Worker
44*523fa7a6SAndroid Build Coastguard Worker    [target_base]
45*523fa7a6SAndroid Build Coastguard Worker    excludes = [
46*523fa7a6SAndroid Build Coastguard Worker    "^third-party",
47*523fa7a6SAndroid Build Coastguard Worker    ]
48*523fa7a6SAndroid Build Coastguard Worker
49*523fa7a6SAndroid Build Coastguard Worker    [targets.schema]
50*523fa7a6SAndroid Build Coastguard Worker    buck_targets = [
51*523fa7a6SAndroid Build Coastguard Worker    "//schema:schema",
52*523fa7a6SAndroid Build Coastguard Worker    ]
53*523fa7a6SAndroid Build Coastguard Worker    filters = [
54*523fa7a6SAndroid Build Coastguard Worker    ".fbs$",
55*523fa7a6SAndroid Build Coastguard Worker    ]
56*523fa7a6SAndroid Build Coastguard Worker
57*523fa7a6SAndroid Build Coastguard Worker    [targets.executorch]
58*523fa7a6SAndroid Build Coastguard Worker    buck_targets = [
59*523fa7a6SAndroid Build Coastguard Worker    "//runtime/executor:program",
60*523fa7a6SAndroid Build Coastguard Worker    ]
61*523fa7a6SAndroid Build Coastguard Worker    deps = [
62*523fa7a6SAndroid Build Coastguard Worker    "schema",
63*523fa7a6SAndroid Build Coastguard Worker    ]
64*523fa7a6SAndroid Build Coastguard Worker    filters = [
65*523fa7a6SAndroid Build Coastguard Worker    ".cpp$",
66*523fa7a6SAndroid Build Coastguard Worker    ]
67*523fa7a6SAndroid Build Coastguard Worker"""
68*523fa7a6SAndroid Build Coastguard Worker
69*523fa7a6SAndroid Build Coastguard Worker
70*523fa7a6SAndroid Build Coastguard Workerclass Target:
71*523fa7a6SAndroid Build Coastguard Worker    """Parsed [targets.*] entry from the TOML file.
72*523fa7a6SAndroid Build Coastguard Worker
73*523fa7a6SAndroid Build Coastguard Worker    Can query buck for its list of source files.
74*523fa7a6SAndroid Build Coastguard Worker    """
75*523fa7a6SAndroid Build Coastguard Worker
76*523fa7a6SAndroid Build Coastguard Worker    class _InitState(Enum):
77*523fa7a6SAndroid Build Coastguard Worker        UNINITIALIZED = 0
78*523fa7a6SAndroid Build Coastguard Worker        INITIALIZING = 1
79*523fa7a6SAndroid Build Coastguard Worker        READY = 2
80*523fa7a6SAndroid Build Coastguard Worker
81*523fa7a6SAndroid Build Coastguard Worker    def __init__(
82*523fa7a6SAndroid Build Coastguard Worker        self,
83*523fa7a6SAndroid Build Coastguard Worker        name: str,
84*523fa7a6SAndroid Build Coastguard Worker        target_dict: dict[str, Sequence[str]],
85*523fa7a6SAndroid Build Coastguard Worker        base_dict: Optional[dict] = None,
86*523fa7a6SAndroid Build Coastguard Worker    ) -> None:
87*523fa7a6SAndroid Build Coastguard Worker        self._state: Target._InitState = Target._InitState.UNINITIALIZED
88*523fa7a6SAndroid Build Coastguard Worker        self._sources = frozenset()
89*523fa7a6SAndroid Build Coastguard Worker
90*523fa7a6SAndroid Build Coastguard Worker        self.name = name
91*523fa7a6SAndroid Build Coastguard Worker        # Extend the base lists with the target-specific entries.
92*523fa7a6SAndroid Build Coastguard Worker        self._config = copy.deepcopy(base_dict or {})
93*523fa7a6SAndroid Build Coastguard Worker        for k, v in target_dict.items():
94*523fa7a6SAndroid Build Coastguard Worker            if k in self._config:
95*523fa7a6SAndroid Build Coastguard Worker                self._config[k].extend(v)
96*523fa7a6SAndroid Build Coastguard Worker            else:
97*523fa7a6SAndroid Build Coastguard Worker                self._config[k] = v
98*523fa7a6SAndroid Build Coastguard Worker
99*523fa7a6SAndroid Build Coastguard Worker    def get_sources(
100*523fa7a6SAndroid Build Coastguard Worker        self, graph: "Graph", runner: Buck2Runner, buck_args: Optional[List[str]]
101*523fa7a6SAndroid Build Coastguard Worker    ) -> frozenset[str]:
102*523fa7a6SAndroid Build Coastguard Worker        if buck_args is None:
103*523fa7a6SAndroid Build Coastguard Worker            buck_args = []
104*523fa7a6SAndroid Build Coastguard Worker
105*523fa7a6SAndroid Build Coastguard Worker        if self._state == Target._InitState.READY:
106*523fa7a6SAndroid Build Coastguard Worker            return self._sources
107*523fa7a6SAndroid Build Coastguard Worker        # Detect cycles.
108*523fa7a6SAndroid Build Coastguard Worker        assert self._state != Target._InitState.INITIALIZING
109*523fa7a6SAndroid Build Coastguard Worker
110*523fa7a6SAndroid Build Coastguard Worker        # Assemble the query.
111*523fa7a6SAndroid Build Coastguard Worker        query = "inputs({})".format(
112*523fa7a6SAndroid Build Coastguard Worker            "+".join(
113*523fa7a6SAndroid Build Coastguard Worker                [
114*523fa7a6SAndroid Build Coastguard Worker                    "deps('{}')".format(target)
115*523fa7a6SAndroid Build Coastguard Worker                    for target in self._config.get("buck_targets", [])
116*523fa7a6SAndroid Build Coastguard Worker                ]
117*523fa7a6SAndroid Build Coastguard Worker            )
118*523fa7a6SAndroid Build Coastguard Worker        )
119*523fa7a6SAndroid Build Coastguard Worker
120*523fa7a6SAndroid Build Coastguard Worker        # Get the complete list of source files that this target depends on.
121*523fa7a6SAndroid Build Coastguard Worker        sources: set[str] = set(runner.run(["cquery", query] + buck_args))
122*523fa7a6SAndroid Build Coastguard Worker
123*523fa7a6SAndroid Build Coastguard Worker        # Keep entries that match all of the filters.
124*523fa7a6SAndroid Build Coastguard Worker        filters = [re.compile(p) for p in self._config.get("filters", [])]
125*523fa7a6SAndroid Build Coastguard Worker        sources = {s for s in sources if all(p.search(s) for p in filters)}
126*523fa7a6SAndroid Build Coastguard Worker
127*523fa7a6SAndroid Build Coastguard Worker        # Remove entries that match any of the excludes.
128*523fa7a6SAndroid Build Coastguard Worker        excludes = [re.compile(p) for p in self._config.get("excludes", [])]
129*523fa7a6SAndroid Build Coastguard Worker        sources = {s for s in sources if not any(p.search(s) for p in excludes)}
130*523fa7a6SAndroid Build Coastguard Worker
131*523fa7a6SAndroid Build Coastguard Worker        # The buck query will give us the complete list of sources that this
132*523fa7a6SAndroid Build Coastguard Worker        # target depends on, but that list includes sources that are owned by
133*523fa7a6SAndroid Build Coastguard Worker        # its deps. Remove entries that are already covered by the transitive
134*523fa7a6SAndroid Build Coastguard Worker        # set of dependencies.
135*523fa7a6SAndroid Build Coastguard Worker        for dep in self._config.get("deps", []):
136*523fa7a6SAndroid Build Coastguard Worker            sources.difference_update(
137*523fa7a6SAndroid Build Coastguard Worker                graph.by_name[dep].get_sources(graph, runner, buck_args)
138*523fa7a6SAndroid Build Coastguard Worker            )
139*523fa7a6SAndroid Build Coastguard Worker
140*523fa7a6SAndroid Build Coastguard Worker        self._sources = frozenset(sources)
141*523fa7a6SAndroid Build Coastguard Worker        self._state = Target._InitState.READY
142*523fa7a6SAndroid Build Coastguard Worker        return self._sources
143*523fa7a6SAndroid Build Coastguard Worker
144*523fa7a6SAndroid Build Coastguard Worker
145*523fa7a6SAndroid Build Coastguard Workerclass Graph:
146*523fa7a6SAndroid Build Coastguard Worker    """Graph of targets."""
147*523fa7a6SAndroid Build Coastguard Worker
148*523fa7a6SAndroid Build Coastguard Worker    def __init__(self, config_dict: dict[str, Any]) -> None:
149*523fa7a6SAndroid Build Coastguard Worker        base = config_dict.get("target_base", {})
150*523fa7a6SAndroid Build Coastguard Worker        targets = config_dict.get("targets", {})
151*523fa7a6SAndroid Build Coastguard Worker
152*523fa7a6SAndroid Build Coastguard Worker        self.by_name = {}
153*523fa7a6SAndroid Build Coastguard Worker        for k, v in targets.items():
154*523fa7a6SAndroid Build Coastguard Worker            self.by_name[k] = Target(k, v, base)
155*523fa7a6SAndroid Build Coastguard Worker
156*523fa7a6SAndroid Build Coastguard Worker
157*523fa7a6SAndroid Build Coastguard Workerdef parse_args() -> argparse.Namespace:
158*523fa7a6SAndroid Build Coastguard Worker    parser = argparse.ArgumentParser(
159*523fa7a6SAndroid Build Coastguard Worker        description="Extracts deps from the buck2 build system",
160*523fa7a6SAndroid Build Coastguard Worker    )
161*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
162*523fa7a6SAndroid Build Coastguard Worker        "--buck2",
163*523fa7a6SAndroid Build Coastguard Worker        default="buck2",
164*523fa7a6SAndroid Build Coastguard Worker        help="'buck2' command to use",
165*523fa7a6SAndroid Build Coastguard Worker    )
166*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
167*523fa7a6SAndroid Build Coastguard Worker        "--config",
168*523fa7a6SAndroid Build Coastguard Worker        metavar="config.toml",
169*523fa7a6SAndroid Build Coastguard Worker        required=True,
170*523fa7a6SAndroid Build Coastguard Worker        help="Path to the input TOML configuration file",
171*523fa7a6SAndroid Build Coastguard Worker    )
172*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
173*523fa7a6SAndroid Build Coastguard Worker        "--format",
174*523fa7a6SAndroid Build Coastguard Worker        default="cmake",
175*523fa7a6SAndroid Build Coastguard Worker        choices=["cmake"],
176*523fa7a6SAndroid Build Coastguard Worker        help="Format to generate.",
177*523fa7a6SAndroid Build Coastguard Worker    )
178*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
179*523fa7a6SAndroid Build Coastguard Worker        "--out",
180*523fa7a6SAndroid Build Coastguard Worker        metavar="file",
181*523fa7a6SAndroid Build Coastguard Worker        help="Path to the file to generate.",
182*523fa7a6SAndroid Build Coastguard Worker    )
183*523fa7a6SAndroid Build Coastguard Worker    parser.add_argument(
184*523fa7a6SAndroid Build Coastguard Worker        "--target-platforms", help="--target-platforms to pass to buck cquery, if any."
185*523fa7a6SAndroid Build Coastguard Worker    )
186*523fa7a6SAndroid Build Coastguard Worker    return parser.parse_args()
187*523fa7a6SAndroid Build Coastguard Worker
188*523fa7a6SAndroid Build Coastguard Worker
189*523fa7a6SAndroid Build Coastguard Workerdef generate_cmake(target_to_srcs: dict[str, list[str]]) -> bytes:
190*523fa7a6SAndroid Build Coastguard Worker    lines: list[str] = []
191*523fa7a6SAndroid Build Coastguard Worker    lines.append("# @" + f"generated by {os.path.basename(__file__)}")
192*523fa7a6SAndroid Build Coastguard Worker    for target, srcs in target_to_srcs.items():
193*523fa7a6SAndroid Build Coastguard Worker        lines.append("")
194*523fa7a6SAndroid Build Coastguard Worker        lines.append(f"set(_{target}__srcs")
195*523fa7a6SAndroid Build Coastguard Worker        for src in srcs:
196*523fa7a6SAndroid Build Coastguard Worker            lines.append(f"    {src}")
197*523fa7a6SAndroid Build Coastguard Worker        lines.append(")")
198*523fa7a6SAndroid Build Coastguard Worker    return "\n".join(lines).encode("utf-8")
199*523fa7a6SAndroid Build Coastguard Worker
200*523fa7a6SAndroid Build Coastguard Worker
201*523fa7a6SAndroid Build Coastguard Workerdef main():
202*523fa7a6SAndroid Build Coastguard Worker    args = parse_args()
203*523fa7a6SAndroid Build Coastguard Worker
204*523fa7a6SAndroid Build Coastguard Worker    # Load and parse the TOML configuration
205*523fa7a6SAndroid Build Coastguard Worker    with open(args.config, mode="rb") as fp:
206*523fa7a6SAndroid Build Coastguard Worker        config_dict = tomllib.load(fp)
207*523fa7a6SAndroid Build Coastguard Worker    graph = Graph(config_dict)
208*523fa7a6SAndroid Build Coastguard Worker
209*523fa7a6SAndroid Build Coastguard Worker    # Run the queries and get the lists of source files.
210*523fa7a6SAndroid Build Coastguard Worker    target_to_srcs: dict[str, list[str]] = {}
211*523fa7a6SAndroid Build Coastguard Worker    runner: Buck2Runner = Buck2Runner(args.buck2)
212*523fa7a6SAndroid Build Coastguard Worker    buck_args = []
213*523fa7a6SAndroid Build Coastguard Worker    if args.target_platforms:
214*523fa7a6SAndroid Build Coastguard Worker        buck_args = ["--target-platforms"]
215*523fa7a6SAndroid Build Coastguard Worker        buck_args.append(args.target_platforms)
216*523fa7a6SAndroid Build Coastguard Worker    for name, target in graph.by_name.items():
217*523fa7a6SAndroid Build Coastguard Worker        target_to_srcs[name] = sorted(target.get_sources(graph, runner, buck_args))
218*523fa7a6SAndroid Build Coastguard Worker
219*523fa7a6SAndroid Build Coastguard Worker    # Generate the requested format.
220*523fa7a6SAndroid Build Coastguard Worker    output: bytes
221*523fa7a6SAndroid Build Coastguard Worker    if args.format == "cmake":
222*523fa7a6SAndroid Build Coastguard Worker        output = generate_cmake(target_to_srcs)
223*523fa7a6SAndroid Build Coastguard Worker    else:
224*523fa7a6SAndroid Build Coastguard Worker        raise ValueError("Unknown format: {}".format(args.format))
225*523fa7a6SAndroid Build Coastguard Worker
226*523fa7a6SAndroid Build Coastguard Worker    # Write the output.
227*523fa7a6SAndroid Build Coastguard Worker    with open(args.out, "wb") as fp:
228*523fa7a6SAndroid Build Coastguard Worker        fp.write(output)
229*523fa7a6SAndroid Build Coastguard Worker
230*523fa7a6SAndroid Build Coastguard Worker
231*523fa7a6SAndroid Build Coastguard Workerif __name__ == "__main__":
232*523fa7a6SAndroid Build Coastguard Worker    main()
233