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""" 9Prints the headers that are listed as exported headers for the 10provided targets, including their exported deps recursively. 11""" 12 13import argparse 14import json 15import os 16import subprocess 17from typing import List, Set 18 19# Run buck2 from the same directory (and thus repo) as this script. 20BUCK_CWD: str = os.path.dirname(os.path.realpath(__file__)) 21 22 23def run(command: List[str]) -> str: 24 """Run subprocess and return its output.""" 25 result = subprocess.run(command, capture_output=True, check=True, cwd=BUCK_CWD) 26 return result.stdout.decode() 27 28 29def query(buck2: str, target: str, attribute: str) -> str: 30 """Query an attribute of a target.""" 31 output = run([buck2, "cquery", target, "--output-attribute", attribute]) 32 33 try: 34 output_json = json.loads(output) 35 return output_json[next(iter(output_json))][attribute] 36 except (json.JSONDecodeError, KeyError) as e: 37 print(f"Failed to parse JSON from query({target}, {attribute}): {output}") 38 raise SystemExit("Error: " + str(e)) 39 40 41def exported_headers(buck2: str, target: str) -> Set[str]: 42 """Get all exported headers of a target and its dependencies.""" 43 deps = query(buck2, target, "exported_deps") 44 headers = set(query(buck2, target, "exported_headers")) 45 headers.update( 46 header 47 for dep in deps 48 for header in exported_headers(buck2, dep.split()[0]) 49 if header.endswith(".h") 50 ) 51 return headers 52 53 54def expand_target(buck2: str, target: str) -> List[str]: 55 """Expand a target into a list of targets if applicable.""" 56 output = run([buck2, "cquery", target]) 57 # Buck's output format is "<target> (<build platform>)", we take only the target part. 58 targets = [line.split(" ")[0] for line in output.strip().split("\n")] 59 return targets 60 61 62def main(): 63 parser = argparse.ArgumentParser() 64 parser.add_argument( 65 "--buck2", default="buck2", help="Path to the buck2 executable." 66 ) 67 parser.add_argument( 68 "--targets", 69 nargs="+", 70 required=True, 71 help="Buck targets to find the headers of.", 72 ) 73 args = parser.parse_args() 74 75 targets = [ 76 target 77 for input_target in args.targets 78 for target in expand_target(args.buck2, input_target) 79 ] 80 81 # Use a set to remove duplicates. 82 headers = { 83 header for target in targets for header in exported_headers(args.buck2, target) 84 } 85 86 for header in sorted(headers): 87 # Strip off the leading workspace name and //. 88 print(header.split("//", 1)[-1]) 89 90 91if __name__ == "__main__": 92 main() 93