| #!/usr/bin/env python3 | |
| # Copyright 2023 Google LLC | |
| # | |
| # Licensed under the Apache License, Version 2.0 (the "License"); | |
| # you may not use this file except in compliance with the License. | |
| # You may obtain a copy of the License at | |
| # | |
| # http://www.apache.org/licenses/LICENSE-2.0 | |
| # | |
| # Unless required by applicable law or agreed to in writing, software | |
| # distributed under the License is distributed on an "AS IS" BASIS, | |
| # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | |
| # See the License for the specific language governing permissions and | |
| # limitations under the License. | |
| # Experimental script that generates CMakeLists.txt files from BUILD files. | |
| # | |
| # Requires: python3, jinja2, and bazel. | |
| # | |
| # Install jinja2 with `sudo apt install python3-jinja2` or `pip install Jinja2`. | |
| # | |
| # Usage: | |
| # | |
| # ./generate_cmake_from_bazel.py | |
| import jinja2 | |
| import os | |
| import subprocess | |
| import sys | |
| import xml.etree.ElementTree as ET | |
| DIRECTORIES_TO_PROCESS = [ | |
| # TODO(lszekeres): List all. | |
| "fuzztest", | |
| "fuzztest/grammars", | |
| "domain_tests", | |
| ] | |
| TRANSLATE_NAME = { | |
| "//centipede:centipede_runner_no_main": "TODO", | |
| "@abseil-cpp//absl/algorithm:container": "absl::algorithm_container", | |
| "@abseil-cpp//absl/base:core_headers": "absl::core_headers", | |
| "@abseil-cpp//absl/container:flat_hash_map": "absl::flat_hash_map", | |
| "@abseil-cpp//absl/container:flat_hash_set": "absl::flat_hash_set", | |
| "@abseil-cpp//absl/debugging:symbolize": "absl::symbolize", | |
| "@abseil-cpp//absl/flags:flag": "absl::flags", | |
| "@abseil-cpp//absl/flags:parse": "absl::flags_parse", | |
| "@abseil-cpp//absl/functional:any_invocable": "absl::any_invocable", | |
| "@abseil-cpp//absl/functional:function_ref": "absl::function_ref", | |
| "@abseil-cpp//absl/hash:hash": "absl::hash", | |
| "@abseil-cpp//absl/log:check": "absl::log", | |
| "@abseil-cpp//absl/memory:memory": "absl::memory", | |
| "@abseil-cpp//absl/numeric:bits": "absl::bits", | |
| "@abseil-cpp//absl/numeric:int128": "absl::int128", | |
| "@abseil-cpp//absl/random:bit_gen_ref": "absl::random_bit_gen_ref", | |
| "@abseil-cpp//absl/random:distributions": "absl::random_distributions", | |
| "@abseil-cpp//absl/random:random": "absl::random_random", | |
| "@abseil-cpp//absl/status:status": "absl::status", | |
| "@abseil-cpp//absl/strings:cord": "absl::cord", | |
| "@abseil-cpp//absl/strings:str_format": "absl::str_format", | |
| "@abseil-cpp//absl/strings:string_view": "absl::string_view", | |
| "@abseil-cpp//absl/strings:strings": "absl::strings", | |
| "@abseil-cpp//absl/synchronization:synchronization": "absl::synchronization", | |
| "@abseil-cpp//absl/time:time": "absl::time", | |
| "@abseil-cpp//absl/types:optional": "absl::optional", | |
| "@abseil-cpp//absl/types:span": "absl::span", | |
| "@abseil-cpp//absl/types:variant": "absl::variant", | |
| "@flatbuffers//:runtime_cc": "flatbuffers", | |
| "@googletest//:gtest": "GTest::gtest", | |
| "@googletest//:gtest_main": "GTest::gmock_main", | |
| "@protobuf//:protobuf": "protobuf::libprotobuf", | |
| "@re2//:re2": "re2::re2", | |
| "@nlohmann_json//:json": "nlohmann_json", | |
| } | |
| # Returns fuzztest package if label belongs to fuzztest. | |
| def get_fuzztest_package(label): | |
| for directory in DIRECTORIES_TO_PROCESS: | |
| package = '//' + directory + ':' | |
| if label.startswith(package): | |
| return package | |
| return None | |
| def get_query_xml(directory): | |
| command = ["bazel", "query", "--output=xml", ":all"] | |
| output = subprocess.check_output(command, cwd=directory, stderr=subprocess.PIPE) | |
| return output | |
| def parse_xml(package, query_xml): | |
| targets = [] | |
| xml_root = ET.fromstring(query_xml) | |
| for xml_target in xml_root.findall(".//rule"): | |
| target = {} | |
| target["name"] = xml_target.get("name").removeprefix(package) | |
| target["type"] = xml_target.get("class") | |
| # TODO(lszekeres): Support fuztest_cc_binary, fuzztest_proto_library, | |
| # fuzztest_cc_proto_library, etc. | |
| if target["type"] not in ["cc_library", "cc_test"]: | |
| continue | |
| for attribute in ["deps", "hdrs", "srcs"]: | |
| xml_list = xml_target.find('.//list[@name="' + attribute + '"]') | |
| if xml_list: | |
| target[attribute] = [] | |
| for xml_label in xml_list.findall(".//label"): | |
| label = xml_label.get("value") | |
| if attribute == "deps": | |
| fuzztest_package = get_fuzztest_package(label) | |
| if fuzztest_package: | |
| label = label.removeprefix(fuzztest_package) | |
| label = "fuzztest::" + label | |
| else: # non-fuzztest dependency | |
| label = TRANSLATE_NAME[label] | |
| else: # hdrs or srcs | |
| label = label.removeprefix(package) | |
| target[attribute].append(label) | |
| targets.append(target) | |
| return targets | |
| def render(template_path, targets): | |
| with open(template_path, "r") as template_file: | |
| template = jinja2.Template(template_file.read()) | |
| rendered_template = template.render(targets=targets) | |
| print(rendered_template) | |
| if __name__ == "__main__": | |
| script_path = os.path.abspath(__file__) | |
| script_dir_path = os.path.dirname(script_path) | |
| repo_path = os.path.dirname(script_dir_path) | |
| for directory in DIRECTORIES_TO_PROCESS: | |
| absolute_dir = os.path.join(repo_path, directory) | |
| relative_dir = os.path.relpath(absolute_dir, repo_path) | |
| package = '//' + relative_dir + ':' | |
| print("\n###################################################") | |
| print(package) | |
| print("###################################################") | |
| query_xml = get_query_xml(absolute_dir) | |
| targets = parse_xml(package, query_xml) | |
| # TODO(lszekeres): Directly update CMakeLists.txt files. | |
| template_path = os.path.join(script_dir_path, "CMakeLists.txt.jinja") | |
| render(template_path, targets) |