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