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