tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

abseil.podspec.gen.py (7785B)


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