blob: 86bfd593842a33eb63d578472c5d5fb1d8c76dc0 [file] [log] [blame]
#!/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)