Abseil Common Libraries (C++) (grcp 依赖)
https://abseil.io/
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
229 lines
7.2 KiB
229 lines
7.2 KiB
#!/usr/bin/env python3 |
|
# -*- coding: utf-8 -*- |
|
"""This script generates abseil.podspec from all BUILD.bazel files. |
|
|
|
This is expected to run on abseil git repository with Bazel 1.0 on Linux. |
|
It recursively analyzes BUILD.bazel files using query command of Bazel to |
|
dump its build rules in XML format. From these rules, it constructs podspec |
|
structure. |
|
""" |
|
|
|
import argparse |
|
import collections |
|
import os |
|
import re |
|
import subprocess |
|
import xml.etree.ElementTree |
|
|
|
# Template of root podspec. |
|
SPEC_TEMPLATE = """ |
|
# This file has been automatically generated from a script. |
|
# Please make modifications to `abseil.podspec.gen.py` instead. |
|
Pod::Spec.new do |s| |
|
s.name = 'abseil' |
|
s.version = '${version}' |
|
s.summary = 'Abseil Common Libraries (C++) from Google' |
|
s.homepage = 'https://abseil.io' |
|
s.license = 'Apache License, Version 2.0' |
|
s.authors = { 'Abseil Team' => 'abseil-io@googlegroups.com' } |
|
s.source = { |
|
:git => 'https://github.com/abseil/abseil-cpp.git', |
|
:tag => '${tag}', |
|
} |
|
s.module_name = 'absl' |
|
s.header_mappings_dir = 'absl' |
|
s.header_dir = 'absl' |
|
s.libraries = 'c++' |
|
s.compiler_flags = '-Wno-everything' |
|
s.pod_target_xcconfig = { |
|
'USER_HEADER_SEARCH_PATHS' => '$(inherited) "$(PODS_TARGET_SRCROOT)"', |
|
'USE_HEADERMAP' => 'NO', |
|
'ALWAYS_SEARCH_USER_PATHS' => 'NO', |
|
} |
|
s.ios.deployment_target = '7.0' |
|
s.osx.deployment_target = '10.9' |
|
s.tvos.deployment_target = '9.0' |
|
s.watchos.deployment_target = '2.0' |
|
""" |
|
|
|
# Rule object representing the rule of Bazel BUILD. |
|
Rule = collections.namedtuple( |
|
"Rule", "type name package srcs hdrs textual_hdrs deps visibility testonly") |
|
|
|
|
|
def get_elem_value(elem, name): |
|
"""Returns the value of XML element with the given name.""" |
|
for child in elem: |
|
if child.attrib.get("name") != name: |
|
continue |
|
if child.tag == "string": |
|
return child.attrib.get("value") |
|
if child.tag == "boolean": |
|
return child.attrib.get("value") == "true" |
|
if child.tag == "list": |
|
return [nested_child.attrib.get("value") for nested_child in child] |
|
raise "Cannot recognize tag: " + child.tag |
|
return None |
|
|
|
|
|
def normalize_paths(paths): |
|
"""Returns the list of normalized path.""" |
|
# e.g. ["//absl/strings:dir/header.h"] -> ["absl/strings/dir/header.h"] |
|
return [path.lstrip("/").replace(":", "/") for path in paths] |
|
|
|
|
|
def parse_rule(elem, package): |
|
"""Returns a rule from bazel XML rule.""" |
|
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_build(package): |
|
"""Runs bazel query on given package file and returns all cc rules.""" |
|
result = subprocess.check_output( |
|
["bazel", "query", package + ":all", "--output", "xml"]) |
|
root = xml.etree.ElementTree.fromstring(result) |
|
return [ |
|
parse_rule(elem, package) |
|
for elem in root |
|
if elem.tag == "rule" and elem.attrib["class"].startswith("cc_") |
|
] |
|
|
|
|
|
def collect_rules(root_path): |
|
"""Collects and returns all rules from root path recursively.""" |
|
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_build("//" + cur)) |
|
return rules |
|
|
|
|
|
def relevant_rule(rule): |
|
"""Returns true if a given rule is relevant when generating a podspec.""" |
|
return ( |
|
# cc_library only (ignore cc_test, cc_binary) |
|
rule.type == "cc_library" and |
|
# ignore empty rule |
|
(rule.hdrs + rule.textual_hdrs + rule.srcs) and |
|
# ignore test-only rule |
|
not rule.testonly) |
|
|
|
|
|
def get_spec_var(depth): |
|
"""Returns the name of variable for spec with given depth.""" |
|
return "s" if depth == 0 else "s{}".format(depth) |
|
|
|
|
|
def get_spec_name(label): |
|
"""Converts the label of bazel rule to the name of podspec.""" |
|
assert label.startswith("//absl/"), "{} doesn't start with //absl/".format( |
|
label) |
|
# e.g. //absl/apple/banana -> abseil/apple/banana |
|
return "abseil/" + label[7:] |
|
|
|
|
|
def write_podspec(f, rules, args): |
|
"""Writes a podspec from given rules and args.""" |
|
rule_dir = build_rule_directory(rules)["abseil"] |
|
# Write root part with given arguments |
|
spec = re.sub(r"\$\{(\w+)\}", lambda x: args[x.group(1)], |
|
SPEC_TEMPLATE).lstrip() |
|
f.write(spec) |
|
# Write all target rules |
|
write_podspec_map(f, rule_dir, 0) |
|
f.write("end\n") |
|
|
|
|
|
def build_rule_directory(rules): |
|
"""Builds a tree-style rule directory from given rules.""" |
|
rule_dir = {} |
|
for rule in rules: |
|
cur = rule_dir |
|
for frag in get_spec_name(rule.package).split("/"): |
|
cur = cur.setdefault(frag, {}) |
|
cur[rule.name] = rule |
|
return rule_dir |
|
|
|
|
|
def write_podspec_map(f, cur_map, depth): |
|
"""Writes podspec from rule map recursively.""" |
|
for key, value in sorted(cur_map.items()): |
|
indent = " " * (depth + 1) |
|
f.write("{indent}{var0}.subspec '{key}' do |{var1}|\n".format( |
|
indent=indent, |
|
key=key, |
|
var0=get_spec_var(depth), |
|
var1=get_spec_var(depth + 1))) |
|
if isinstance(value, dict): |
|
write_podspec_map(f, value, depth + 1) |
|
else: |
|
write_podspec_rule(f, value, depth + 1) |
|
f.write("{indent}end\n".format(indent=indent)) |
|
|
|
|
|
def write_podspec_rule(f, rule, depth): |
|
"""Writes podspec from given rule.""" |
|
indent = " " * (depth + 1) |
|
spec_var = get_spec_var(depth) |
|
# Puts all files in hdrs, textual_hdrs, and srcs into source_files. |
|
# Since CocoaPods treats header_files a bit differently from bazel, |
|
# this won't generate a header_files field so that all source_files |
|
# are considered as header files. |
|
srcs = sorted(set(rule.hdrs + rule.textual_hdrs + rule.srcs)) |
|
write_indented_list( |
|
f, "{indent}{var}.source_files = ".format(indent=indent, var=spec_var), |
|
srcs) |
|
# Writes dependencies of this rule. |
|
for dep in sorted(rule.deps): |
|
name = get_spec_name(dep.replace(":", "/")) |
|
f.write("{indent}{var}.dependency '{dep}'\n".format( |
|
indent=indent, var=spec_var, dep=name)) |
|
|
|
|
|
def write_indented_list(f, leading, values): |
|
"""Writes leading values in an indented style.""" |
|
f.write(leading) |
|
f.write((",\n" + " " * len(leading)).join("'{}'".format(v) for v in values)) |
|
f.write("\n") |
|
|
|
|
|
def generate(args): |
|
"""Generates a podspec file from all BUILD files under absl directory.""" |
|
rules = filter(relevant_rule, collect_rules("absl")) |
|
with open(args.output, "wt") as f: |
|
write_podspec(f, rules, vars(args)) |
|
|
|
|
|
def main(): |
|
parser = argparse.ArgumentParser( |
|
description="Generates abseil.podspec from BUILD.bazel") |
|
parser.add_argument( |
|
"-v", "--version", help="The version of podspec", required=True) |
|
parser.add_argument( |
|
"-t", |
|
"--tag", |
|
default=None, |
|
help="The name of git tag (default: version)") |
|
parser.add_argument( |
|
"-o", |
|
"--output", |
|
default="abseil.podspec", |
|
help="The name of output file (default: abseil.podspec)") |
|
args = parser.parse_args() |
|
if args.tag is None: |
|
args.tag = args.version |
|
generate(args) |
|
|
|
|
|
if __name__ == "__main__": |
|
main()
|
|
|