import collections
import os
import re
import subprocess
import xml.etree.ElementTree as ET
import yaml
ABSEIL_PATH = "third_party/abseil-cpp"
OUTPUT_PATH = "src/abseil-cpp/preprocessed_builds.yaml"
CAPITAL_WORD = re.compile("[A-Z]+")
ABSEIL_CMAKE_RULE_BEGIN = re.compile("^absl_cc_.*\(", re.MULTILINE)
ABSEIL_CMAKE_RULE_END = re.compile("^\)", re.MULTILINE)
Rule = collections.namedtuple(
"Rule", "type name package srcs hdrs textual_hdrs deps visibility testonly")
def get_elem_value(elem, name):
for child in elem:
if child.attrib.get("name") == name:
if child.tag == "string":
return child.attrib.get("value")
elif child.tag == "boolean":
return child.attrib.get("value") == "true"
elif child.tag == "list":
return [nested_child.attrib.get("value") for nested_child in child]
else:
raise "Cannot recognize tag: " + child.tag
return None
def normalize_paths(paths):
return [path.lstrip("/").replace(":", "/") for path in paths]
def parse_bazel_rule(elem, package):
return Rule(
type=elem.attrib["class"],
name=get_elem_value(elem, "name"),
package=package,
srcs=normalize_paths(get_elem_value(elem, "srcs") or []),
hdrs=normalize_paths(get_elem_value(elem, "hdrs") or []),
textual_hdrs=normalize_paths(get_elem_value(elem, "textual_hdrs") or []),
deps=get_elem_value(elem, "deps") or [],
visibility=get_elem_value(elem, "visibility") or [],
testonly=get_elem_value(elem, "testonly") or False)
def read_bazel_build(package):
BAZEL_BIN = "../../tools/bazel"
result = subprocess.check_output(
[BAZEL_BIN, "query", package + ":all", "--output", "xml"])
root = ET.fromstring(result)
return [
parse_bazel_rule(elem, package)
for elem in root
if elem.tag == "rule" and elem.attrib["class"].startswith("cc_")
]
def collect_bazel_rules(root_path):
rules = []
for cur, _, _ in os.walk(root_path):
build_path = os.path.join(cur, "BUILD.bazel")
if os.path.exists(build_path):
rules.extend(read_bazel_build("//" + cur))
return rules
def parse_cmake_rule(rule, package):
kv = {}
bucket = None
lines = rule.splitlines()
for line in lines[1:-1]:
if CAPITAL_WORD.match(line.strip()):
bucket = kv.setdefault(line.strip(), [])
else:
if bucket is not None:
bucket.append(line.strip())
else:
raise ValueError("Illegal syntax: {}".format(rule))
return Rule(
type=lines[0].rstrip("("),
name="absl::" + kv["NAME"][0],
package=package,
srcs=[package + "/" + f.strip('"') for f in kv.get("SRCS", [])],
hdrs=[package + "/" + f.strip('"') for f in kv.get("HDRS", [])],
textual_hdrs=[],
deps=kv.get("DEPS", []),
visibility="PUBLIC" in kv,
testonly="TESTONLY" in kv,
)
def read_cmake_build(build_path, package):
rules = []
with open(build_path, "r") as f:
src = f.read()
for begin_mo in ABSEIL_CMAKE_RULE_BEGIN.finditer(src):
end_mo = ABSEIL_CMAKE_RULE_END.search(src[begin_mo.start(0):])
expr = src[begin_mo.start(0):begin_mo.start(0) + end_mo.start(0) + 1]
rules.append(parse_cmake_rule(expr, package))
return rules
def collect_cmake_rules(root_path):
rules = []
for cur, _, _ in os.walk(root_path):
build_path = os.path.join(cur, "CMakeLists.txt")
if os.path.exists(build_path):
rules.extend(read_cmake_build(build_path, cur))
return rules
def pairing_bazel_and_cmake_rules(bazel_rules, cmake_rules):
pair_map = {}
for rule in bazel_rules:
best_crule, best_similarity = None, 0
for crule in cmake_rules:
similarity = len(
set(rule.srcs + rule.hdrs + rule.textual_hdrs).intersection(
set(crule.srcs + crule.hdrs + crule.textual_hdrs)))
if similarity > best_similarity:
best_crule, best_similarity = crule, similarity
if best_crule:
pair_map[(rule.package, rule.name)] = best_crule.name
return pair_map
def resolve_hdrs(files):
return [ABSEIL_PATH + "/" + f for f in files if f.endswith((".h", ".inc"))]
def resolve_srcs(files):
return [ABSEIL_PATH + "/" + f for f in files if f.endswith(".cc")]
def resolve_deps(targets):
return [(t[2:] if t.startswith("//") else t) for t in targets]
def generate_builds(root_path):
bazel_rules = list(
filter(lambda r: r.type == "cc_library" and not r.testonly,
collect_bazel_rules(root_path)))
cmake_rules = list(
filter(lambda r: r.type == "absl_cc_library" and not r.testonly,
collect_cmake_rules(root_path)))
pair_map = pairing_bazel_and_cmake_rules(bazel_rules, cmake_rules)
builds = []
for rule in sorted(bazel_rules, key=lambda r: r.package[2:] + ":" + r.name):
p = {
"name":
rule.package[2:] + ":" + rule.name,
"cmake_target":
pair_map.get((rule.package, rule.name)) or "",
"headers":
sorted(resolve_hdrs(rule.srcs + rule.hdrs + rule.textual_hdrs)),
"src":
sorted(resolve_srcs(rule.srcs + rule.hdrs + rule.textual_hdrs)),
"deps":
sorted(resolve_deps(rule.deps)),
}
builds.append(p)
return builds
def main():
previous_dir = os.getcwd()
os.chdir(ABSEIL_PATH)
builds = generate_builds("absl")
os.chdir(previous_dir)
with open(OUTPUT_PATH, 'w') as outfile:
outfile.write(yaml.dump(builds, indent=2))
if __name__ == "__main__":
main()