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