| #!/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) |