123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229 |
- #!/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 = '9.0'
- s.osx.deployment_target = '10.10'
- 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()
|