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