1*a6aa18fbSYabin Cui#!/usr/bin/env python3 2*a6aa18fbSYabin Cui# -*- coding: utf-8 -*- 3*a6aa18fbSYabin Cui"""This script generates abseil.podspec from all BUILD.bazel files. 4*a6aa18fbSYabin Cui 5*a6aa18fbSYabin CuiThis is expected to run on abseil git repository with Bazel 1.0 on Linux. 6*a6aa18fbSYabin CuiIt recursively analyzes BUILD.bazel files using query command of Bazel to 7*a6aa18fbSYabin Cuidump its build rules in XML format. From these rules, it constructs podspec 8*a6aa18fbSYabin Cuistructure. 9*a6aa18fbSYabin Cui""" 10*a6aa18fbSYabin Cui 11*a6aa18fbSYabin Cuiimport argparse 12*a6aa18fbSYabin Cuiimport collections 13*a6aa18fbSYabin Cuiimport os 14*a6aa18fbSYabin Cuiimport re 15*a6aa18fbSYabin Cuiimport subprocess 16*a6aa18fbSYabin Cuiimport xml.etree.ElementTree 17*a6aa18fbSYabin Cui 18*a6aa18fbSYabin Cui# Template of root podspec. 19*a6aa18fbSYabin CuiSPEC_TEMPLATE = """ 20*a6aa18fbSYabin Cui# This file has been automatically generated from a script. 21*a6aa18fbSYabin Cui# Please make modifications to `abseil.podspec.gen.py` instead. 22*a6aa18fbSYabin CuiPod::Spec.new do |s| 23*a6aa18fbSYabin Cui s.name = 'abseil' 24*a6aa18fbSYabin Cui s.version = '${version}' 25*a6aa18fbSYabin Cui s.summary = 'Abseil Common Libraries (C++) from Google' 26*a6aa18fbSYabin Cui s.homepage = 'https://abseil.io' 27*a6aa18fbSYabin Cui s.license = 'Apache License, Version 2.0' 28*a6aa18fbSYabin Cui s.authors = { 'Abseil Team' => '[email protected]' } 29*a6aa18fbSYabin Cui s.source = { 30*a6aa18fbSYabin Cui :git => 'https://github.com/abseil/abseil-cpp.git', 31*a6aa18fbSYabin Cui :tag => '${tag}', 32*a6aa18fbSYabin Cui } 33*a6aa18fbSYabin Cui s.module_name = 'absl' 34*a6aa18fbSYabin Cui s.header_mappings_dir = 'absl' 35*a6aa18fbSYabin Cui s.header_dir = 'absl' 36*a6aa18fbSYabin Cui s.libraries = 'c++' 37*a6aa18fbSYabin Cui s.compiler_flags = '-Wno-everything' 38*a6aa18fbSYabin Cui s.pod_target_xcconfig = { 39*a6aa18fbSYabin Cui 'USER_HEADER_SEARCH_PATHS' => '$(inherited) "$(PODS_TARGET_SRCROOT)"', 40*a6aa18fbSYabin Cui 'USE_HEADERMAP' => 'NO', 41*a6aa18fbSYabin Cui 'ALWAYS_SEARCH_USER_PATHS' => 'NO', 42*a6aa18fbSYabin Cui } 43*a6aa18fbSYabin Cui s.ios.deployment_target = '9.0' 44*a6aa18fbSYabin Cui s.osx.deployment_target = '10.10' 45*a6aa18fbSYabin Cui s.tvos.deployment_target = '9.0' 46*a6aa18fbSYabin Cui s.watchos.deployment_target = '2.0' 47*a6aa18fbSYabin Cui""" 48*a6aa18fbSYabin Cui 49*a6aa18fbSYabin Cui# Rule object representing the rule of Bazel BUILD. 50*a6aa18fbSYabin CuiRule = collections.namedtuple( 51*a6aa18fbSYabin Cui "Rule", "type name package srcs hdrs textual_hdrs deps visibility testonly") 52*a6aa18fbSYabin Cui 53*a6aa18fbSYabin Cui 54*a6aa18fbSYabin Cuidef get_elem_value(elem, name): 55*a6aa18fbSYabin Cui """Returns the value of XML element with the given name.""" 56*a6aa18fbSYabin Cui for child in elem: 57*a6aa18fbSYabin Cui if child.attrib.get("name") != name: 58*a6aa18fbSYabin Cui continue 59*a6aa18fbSYabin Cui if child.tag == "string": 60*a6aa18fbSYabin Cui return child.attrib.get("value") 61*a6aa18fbSYabin Cui if child.tag == "boolean": 62*a6aa18fbSYabin Cui return child.attrib.get("value") == "true" 63*a6aa18fbSYabin Cui if child.tag == "list": 64*a6aa18fbSYabin Cui return [nested_child.attrib.get("value") for nested_child in child] 65*a6aa18fbSYabin Cui raise "Cannot recognize tag: " + child.tag 66*a6aa18fbSYabin Cui return None 67*a6aa18fbSYabin Cui 68*a6aa18fbSYabin Cui 69*a6aa18fbSYabin Cuidef normalize_paths(paths): 70*a6aa18fbSYabin Cui """Returns the list of normalized path.""" 71*a6aa18fbSYabin Cui # e.g. ["//absl/strings:dir/header.h"] -> ["absl/strings/dir/header.h"] 72*a6aa18fbSYabin Cui return [path.lstrip("/").replace(":", "/") for path in paths] 73*a6aa18fbSYabin Cui 74*a6aa18fbSYabin Cui 75*a6aa18fbSYabin Cuidef parse_rule(elem, package): 76*a6aa18fbSYabin Cui """Returns a rule from bazel XML rule.""" 77*a6aa18fbSYabin Cui return Rule( 78*a6aa18fbSYabin Cui type=elem.attrib["class"], 79*a6aa18fbSYabin Cui name=get_elem_value(elem, "name"), 80*a6aa18fbSYabin Cui package=package, 81*a6aa18fbSYabin Cui srcs=normalize_paths(get_elem_value(elem, "srcs") or []), 82*a6aa18fbSYabin Cui hdrs=normalize_paths(get_elem_value(elem, "hdrs") or []), 83*a6aa18fbSYabin Cui textual_hdrs=normalize_paths(get_elem_value(elem, "textual_hdrs") or []), 84*a6aa18fbSYabin Cui deps=get_elem_value(elem, "deps") or [], 85*a6aa18fbSYabin Cui visibility=get_elem_value(elem, "visibility") or [], 86*a6aa18fbSYabin Cui testonly=get_elem_value(elem, "testonly") or False) 87*a6aa18fbSYabin Cui 88*a6aa18fbSYabin Cui 89*a6aa18fbSYabin Cuidef read_build(package): 90*a6aa18fbSYabin Cui """Runs bazel query on given package file and returns all cc rules.""" 91*a6aa18fbSYabin Cui result = subprocess.check_output( 92*a6aa18fbSYabin Cui ["bazel", "query", package + ":all", "--output", "xml"]) 93*a6aa18fbSYabin Cui root = xml.etree.ElementTree.fromstring(result) 94*a6aa18fbSYabin Cui return [ 95*a6aa18fbSYabin Cui parse_rule(elem, package) 96*a6aa18fbSYabin Cui for elem in root 97*a6aa18fbSYabin Cui if elem.tag == "rule" and elem.attrib["class"].startswith("cc_") 98*a6aa18fbSYabin Cui ] 99*a6aa18fbSYabin Cui 100*a6aa18fbSYabin Cui 101*a6aa18fbSYabin Cuidef collect_rules(root_path): 102*a6aa18fbSYabin Cui """Collects and returns all rules from root path recursively.""" 103*a6aa18fbSYabin Cui rules = [] 104*a6aa18fbSYabin Cui for cur, _, _ in os.walk(root_path): 105*a6aa18fbSYabin Cui build_path = os.path.join(cur, "BUILD.bazel") 106*a6aa18fbSYabin Cui if os.path.exists(build_path): 107*a6aa18fbSYabin Cui rules.extend(read_build("//" + cur)) 108*a6aa18fbSYabin Cui return rules 109*a6aa18fbSYabin Cui 110*a6aa18fbSYabin Cui 111*a6aa18fbSYabin Cuidef relevant_rule(rule): 112*a6aa18fbSYabin Cui """Returns true if a given rule is relevant when generating a podspec.""" 113*a6aa18fbSYabin Cui return ( 114*a6aa18fbSYabin Cui # cc_library only (ignore cc_test, cc_binary) 115*a6aa18fbSYabin Cui rule.type == "cc_library" and 116*a6aa18fbSYabin Cui # ignore empty rule 117*a6aa18fbSYabin Cui (rule.hdrs + rule.textual_hdrs + rule.srcs) and 118*a6aa18fbSYabin Cui # ignore test-only rule 119*a6aa18fbSYabin Cui not rule.testonly) 120*a6aa18fbSYabin Cui 121*a6aa18fbSYabin Cui 122*a6aa18fbSYabin Cuidef get_spec_var(depth): 123*a6aa18fbSYabin Cui """Returns the name of variable for spec with given depth.""" 124*a6aa18fbSYabin Cui return "s" if depth == 0 else "s{}".format(depth) 125*a6aa18fbSYabin Cui 126*a6aa18fbSYabin Cui 127*a6aa18fbSYabin Cuidef get_spec_name(label): 128*a6aa18fbSYabin Cui """Converts the label of bazel rule to the name of podspec.""" 129*a6aa18fbSYabin Cui assert label.startswith("//absl/"), "{} doesn't start with //absl/".format( 130*a6aa18fbSYabin Cui label) 131*a6aa18fbSYabin Cui # e.g. //absl/apple/banana -> abseil/apple/banana 132*a6aa18fbSYabin Cui return "abseil/" + label[7:] 133*a6aa18fbSYabin Cui 134*a6aa18fbSYabin Cui 135*a6aa18fbSYabin Cuidef write_podspec(f, rules, args): 136*a6aa18fbSYabin Cui """Writes a podspec from given rules and args.""" 137*a6aa18fbSYabin Cui rule_dir = build_rule_directory(rules)["abseil"] 138*a6aa18fbSYabin Cui # Write root part with given arguments 139*a6aa18fbSYabin Cui spec = re.sub(r"\$\{(\w+)\}", lambda x: args[x.group(1)], 140*a6aa18fbSYabin Cui SPEC_TEMPLATE).lstrip() 141*a6aa18fbSYabin Cui f.write(spec) 142*a6aa18fbSYabin Cui # Write all target rules 143*a6aa18fbSYabin Cui write_podspec_map(f, rule_dir, 0) 144*a6aa18fbSYabin Cui f.write("end\n") 145*a6aa18fbSYabin Cui 146*a6aa18fbSYabin Cui 147*a6aa18fbSYabin Cuidef build_rule_directory(rules): 148*a6aa18fbSYabin Cui """Builds a tree-style rule directory from given rules.""" 149*a6aa18fbSYabin Cui rule_dir = {} 150*a6aa18fbSYabin Cui for rule in rules: 151*a6aa18fbSYabin Cui cur = rule_dir 152*a6aa18fbSYabin Cui for frag in get_spec_name(rule.package).split("/"): 153*a6aa18fbSYabin Cui cur = cur.setdefault(frag, {}) 154*a6aa18fbSYabin Cui cur[rule.name] = rule 155*a6aa18fbSYabin Cui return rule_dir 156*a6aa18fbSYabin Cui 157*a6aa18fbSYabin Cui 158*a6aa18fbSYabin Cuidef write_podspec_map(f, cur_map, depth): 159*a6aa18fbSYabin Cui """Writes podspec from rule map recursively.""" 160*a6aa18fbSYabin Cui for key, value in sorted(cur_map.items()): 161*a6aa18fbSYabin Cui indent = " " * (depth + 1) 162*a6aa18fbSYabin Cui f.write("{indent}{var0}.subspec '{key}' do |{var1}|\n".format( 163*a6aa18fbSYabin Cui indent=indent, 164*a6aa18fbSYabin Cui key=key, 165*a6aa18fbSYabin Cui var0=get_spec_var(depth), 166*a6aa18fbSYabin Cui var1=get_spec_var(depth + 1))) 167*a6aa18fbSYabin Cui if isinstance(value, dict): 168*a6aa18fbSYabin Cui write_podspec_map(f, value, depth + 1) 169*a6aa18fbSYabin Cui else: 170*a6aa18fbSYabin Cui write_podspec_rule(f, value, depth + 1) 171*a6aa18fbSYabin Cui f.write("{indent}end\n".format(indent=indent)) 172*a6aa18fbSYabin Cui 173*a6aa18fbSYabin Cui 174*a6aa18fbSYabin Cuidef write_podspec_rule(f, rule, depth): 175*a6aa18fbSYabin Cui """Writes podspec from given rule.""" 176*a6aa18fbSYabin Cui indent = " " * (depth + 1) 177*a6aa18fbSYabin Cui spec_var = get_spec_var(depth) 178*a6aa18fbSYabin Cui # Puts all files in hdrs, textual_hdrs, and srcs into source_files. 179*a6aa18fbSYabin Cui # Since CocoaPods treats header_files a bit differently from bazel, 180*a6aa18fbSYabin Cui # this won't generate a header_files field so that all source_files 181*a6aa18fbSYabin Cui # are considered as header files. 182*a6aa18fbSYabin Cui srcs = sorted(set(rule.hdrs + rule.textual_hdrs + rule.srcs)) 183*a6aa18fbSYabin Cui write_indented_list( 184*a6aa18fbSYabin Cui f, "{indent}{var}.source_files = ".format(indent=indent, var=spec_var), 185*a6aa18fbSYabin Cui srcs) 186*a6aa18fbSYabin Cui # Writes dependencies of this rule. 187*a6aa18fbSYabin Cui for dep in sorted(rule.deps): 188*a6aa18fbSYabin Cui name = get_spec_name(dep.replace(":", "/")) 189*a6aa18fbSYabin Cui f.write("{indent}{var}.dependency '{dep}'\n".format( 190*a6aa18fbSYabin Cui indent=indent, var=spec_var, dep=name)) 191*a6aa18fbSYabin Cui 192*a6aa18fbSYabin Cui 193*a6aa18fbSYabin Cuidef write_indented_list(f, leading, values): 194*a6aa18fbSYabin Cui """Writes leading values in an indented style.""" 195*a6aa18fbSYabin Cui f.write(leading) 196*a6aa18fbSYabin Cui f.write((",\n" + " " * len(leading)).join("'{}'".format(v) for v in values)) 197*a6aa18fbSYabin Cui f.write("\n") 198*a6aa18fbSYabin Cui 199*a6aa18fbSYabin Cui 200*a6aa18fbSYabin Cuidef generate(args): 201*a6aa18fbSYabin Cui """Generates a podspec file from all BUILD files under absl directory.""" 202*a6aa18fbSYabin Cui rules = filter(relevant_rule, collect_rules("absl")) 203*a6aa18fbSYabin Cui with open(args.output, "wt") as f: 204*a6aa18fbSYabin Cui write_podspec(f, rules, vars(args)) 205*a6aa18fbSYabin Cui 206*a6aa18fbSYabin Cui 207*a6aa18fbSYabin Cuidef main(): 208*a6aa18fbSYabin Cui parser = argparse.ArgumentParser( 209*a6aa18fbSYabin Cui description="Generates abseil.podspec from BUILD.bazel") 210*a6aa18fbSYabin Cui parser.add_argument( 211*a6aa18fbSYabin Cui "-v", "--version", help="The version of podspec", required=True) 212*a6aa18fbSYabin Cui parser.add_argument( 213*a6aa18fbSYabin Cui "-t", 214*a6aa18fbSYabin Cui "--tag", 215*a6aa18fbSYabin Cui default=None, 216*a6aa18fbSYabin Cui help="The name of git tag (default: version)") 217*a6aa18fbSYabin Cui parser.add_argument( 218*a6aa18fbSYabin Cui "-o", 219*a6aa18fbSYabin Cui "--output", 220*a6aa18fbSYabin Cui default="abseil.podspec", 221*a6aa18fbSYabin Cui help="The name of output file (default: abseil.podspec)") 222*a6aa18fbSYabin Cui args = parser.parse_args() 223*a6aa18fbSYabin Cui if args.tag is None: 224*a6aa18fbSYabin Cui args.tag = args.version 225*a6aa18fbSYabin Cui generate(args) 226*a6aa18fbSYabin Cui 227*a6aa18fbSYabin Cui 228*a6aa18fbSYabin Cuiif __name__ == "__main__": 229*a6aa18fbSYabin Cui main() 230