... notably, this includes Abseil's own StatusOr type, which conflicted with our implementation (that was taken from TensorFlow). Change-Id: Ie7d6764b64055caaeb8dc7b6b9d066291e6b538f
		
			
				
	
	
		
			229 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
			
		
		
	
	
			229 lines
		
	
	
	
		
			7.2 KiB
		
	
	
	
		
			Python
		
	
	
		
			Executable file
		
	
	
	
	
| #!/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()
 |