2
0

build_podspecs.py 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289
  1. #!/usr/bin/env python3
  2. # Copyright 2020, gRPC Authors All rights reserved.
  3. #
  4. # Licensed under the Apache License, Version 2.0 (the "License");
  5. # you may not use this file except in compliance with the License.
  6. # You may obtain a copy of the License at
  7. #
  8. # http://www.apache.org/licenses/LICENSE-2.0
  9. #
  10. # Unless required by applicable law or agreed to in writing, software
  11. # distributed under the License is distributed on an "AS IS" BASIS,
  12. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. # See the License for the specific language governing permissions and
  14. # limitations under the License.
  15. import os
  16. import json
  17. import random
  18. import string
  19. import argparse
  20. import subprocess
  21. import sys
  22. class TargetDependency(object):
  23. def __init__(self, name):
  24. self.name = name
  25. def __str__(self):
  26. return "s.dependency '{name}', s.version.to_s".format(name=self.name)
  27. class ProductDependency(object):
  28. def __init__(self, name, lower, upper):
  29. self.name = name
  30. self.lower = lower
  31. self.upper = upper
  32. def __str__(self):
  33. return "s.dependency '{name}', '>= {lower}', '< {upper}'".format(
  34. name=self.name, lower=self.lower, upper=self.upper)
  35. class Pod:
  36. def __init__(self, name, module_name, version, description, dependencies=None, is_plugins_pod=False):
  37. self.name = name
  38. self.module_name = module_name
  39. self.version = version
  40. self.is_plugins_pod = is_plugins_pod
  41. self.dependencies = dependencies if dependencies is not None else []
  42. self.description = description
  43. def as_podspec(self):
  44. print('\n')
  45. print('Building Podspec for %s' % self.name)
  46. print('-' * 80)
  47. indent=' ' * 4
  48. podspec = "Pod::Spec.new do |s|\n\n"
  49. podspec += indent + "s.name = '%s'\n" % self.name
  50. if not self.is_plugins_pod:
  51. podspec += indent + "s.module_name = '%s'\n" % self.module_name
  52. podspec += indent + "s.version = '%s'\n" % self.version
  53. podspec += indent + "s.license = { :type => 'Apache 2.0', :file => 'LICENSE' }\n"
  54. podspec += indent + "s.summary = '%s'\n" % self.description
  55. podspec += indent + "s.homepage = 'https://www.grpc.io'\n"
  56. podspec += indent + "s.authors = { 'The gRPC contributors' => \'grpc-packages@google.com' }\n\n"
  57. podspec += indent + "s.swift_version = '5.2'\n"
  58. podspec += indent + "s.ios.deployment_target = '10.0'\n"
  59. podspec += indent + "s.osx.deployment_target = '10.12'\n"
  60. podspec += indent + "s.tvos.deployment_target = '10.0'\n"
  61. podspec += indent + "s.watchos.deployment_target = '6.0'\n"
  62. if self.is_plugins_pod:
  63. podspec += indent + "s.source = { :http => \"https://github.com/grpc/grpc-swift/releases/download/#{s.version}/protoc-grpc-swift-plugins-#{s.version}.zip\"}\n\n"
  64. podspec += indent + "s.preserve_paths = '*'\n"
  65. else:
  66. podspec += indent + "s.source = { :git => \"https://github.com/grpc/grpc-swift.git\", :tag => s.version }\n\n"
  67. podspec += indent + "s.source_files = 'Sources/%s/**/*.{swift,c,h}'\n" % (self.module_name)
  68. podspec += "\n" if len(self.dependencies) > 0 else ""
  69. for dep in sorted(self.dependencies, key=lambda x: x.name):
  70. podspec += indent + str(dep) + "\n"
  71. podspec += "\nend"
  72. return podspec
  73. class PodManager:
  74. def __init__(self, directory, version, should_publish, package_dump):
  75. self.directory = directory
  76. self.version = version
  77. self.should_publish = should_publish
  78. self.package_dump = package_dump
  79. def write(self, pod, contents):
  80. print(' Writing to %s/%s.podspec ' % (self.directory, pod))
  81. with open('%s/%s.podspec' % (self.directory, pod), 'w') as podspec_file:
  82. podspec_file.write(contents)
  83. def publish(self, pod_name):
  84. subprocess.check_call(['pod', 'repo', 'update'])
  85. print(' Publishing %s.podspec' % (pod_name))
  86. subprocess.check_call(['pod', 'trunk', 'push', '--synchronous',
  87. self.directory + '/' + pod_name + ".podspec"])
  88. def build_pods(self):
  89. cgrpczlib_pod = Pod(
  90. self.pod_name_for_grpc_target('CGRPCZlib'),
  91. 'CGRPCZlib',
  92. self.version,
  93. 'Compression library that provides in-memory compression and decompression functions',
  94. dependencies=self.build_dependency_list('CGRPCZlib')
  95. )
  96. grpc_pod = Pod(
  97. self.pod_name_for_grpc_target('GRPC'),
  98. 'GRPC',
  99. self.version,
  100. 'Swift gRPC code generator plugin and runtime library',
  101. dependencies=self.build_dependency_list('GRPC')
  102. )
  103. grpc_plugins_pod = Pod(
  104. 'gRPC-Swift-Plugins',
  105. '',
  106. self.version,
  107. 'Swift gRPC code generator plugin binaries',
  108. dependencies=[TargetDependency("gRPC-Swift")],
  109. is_plugins_pod=True
  110. )
  111. return [cgrpczlib_pod, grpc_pod, grpc_plugins_pod]
  112. def go(self, start_from):
  113. pods = self.build_pods()
  114. if start_from:
  115. pods = pods[list(pod.name for pod in pods).index(start_from):]
  116. # Create .podspec files and publish
  117. for target in pods:
  118. self.write(target.name, target.as_podspec())
  119. if self.should_publish:
  120. self.publish(target.name)
  121. else:
  122. print(' Skipping Publishing...')
  123. def pod_name_for_package(self, name):
  124. """Return the CocoaPod name for a given Swift package."""
  125. pod_mappings = {
  126. 'swift-log': 'Logging',
  127. 'swift-nio': 'SwiftNIO',
  128. 'swift-nio-extras': 'SwiftNIOExtras',
  129. 'swift-nio-http2': 'SwiftNIOHTTP2',
  130. 'swift-nio-ssl': 'SwiftNIOSSL',
  131. 'swift-nio-transport-services': 'SwiftNIOTransportServices',
  132. 'SwiftProtobuf': 'SwiftProtobuf'
  133. }
  134. return pod_mappings[name]
  135. def pod_name_for_grpc_target(self, name):
  136. """Return the CocoaPod name for a given gRPC Swift target."""
  137. return {
  138. 'GRPC': 'gRPC-Swift',
  139. 'CGRPCZlib': 'CGRPCZlib'
  140. }[name]
  141. def get_package_requirements(self, package_name):
  142. """
  143. Returns the lower and upper bound version requirements for a given
  144. package dependency.
  145. """
  146. for dependency in self.package_dump['dependencies']:
  147. if dependency['name'] == package_name:
  148. # There should only be 1 range.
  149. requirement = dependency['requirement']['range'][0]
  150. return (requirement['lowerBound'], requirement['upperBound'])
  151. # This shouldn't happen.
  152. raise ValueError('Could not find package called', package_name)
  153. def get_dependencies(self, target_name):
  154. """
  155. Returns a tuple of dependency lists for a given target.
  156. The first entry is the list of product dependencies; dependencies on
  157. products from other packages. The second entry is a list of target
  158. dependencies, i.e. dependencies on other targets within the package.
  159. """
  160. for target in self.package_dump['targets']:
  161. if target['name'] == target_name:
  162. product_dependencies = set()
  163. target_dependencies = []
  164. for dependency in target['dependencies']:
  165. if 'product' in dependency:
  166. product_dependencies.add(dependency['product'][1])
  167. elif 'target' in dependency:
  168. target_dependencies.append(dependency['target'][0])
  169. else:
  170. raise ValueError('Unexpected dependency type:', dependency)
  171. return (product_dependencies, target_dependencies)
  172. # This shouldn't happen.
  173. raise ValueError('Could not find dependency called', target_name)
  174. def build_dependency_list(self, target_name):
  175. """
  176. Returns a list of dependencies for the given target.
  177. Dependencies may be either 'TargetDependency' or 'ProductDependency'.
  178. """
  179. product, target = self.get_dependencies(target_name)
  180. dependencies = []
  181. for package_name in product:
  182. (lower, upper) = self.get_package_requirements(package_name)
  183. pod_name = self.pod_name_for_package(package_name)
  184. dependencies.append(ProductDependency(pod_name, lower, upper))
  185. for target_name in target:
  186. pod_name = self.pod_name_for_grpc_target(target_name)
  187. dependencies.append(TargetDependency(pod_name))
  188. return dependencies
  189. def dir_path(path):
  190. if os.path.isdir(path):
  191. return path
  192. raise NotADirectoryError(path)
  193. def main():
  194. parser = argparse.ArgumentParser(description='Build Podspec files for gRPC Swift')
  195. parser.add_argument(
  196. '-p',
  197. '--path',
  198. type=dir_path,
  199. help='The directory where generated podspec files will be saved. If not passed, defaults to the current working directory.'
  200. )
  201. parser.add_argument(
  202. '-u',
  203. '--upload',
  204. action='store_true',
  205. help='Determines if the newly built Podspec files should be pushed.'
  206. )
  207. parser.add_argument(
  208. '-f',
  209. '--start-from',
  210. help='The name of the Podspec to start from.'
  211. )
  212. parser.add_argument('version')
  213. args = parser.parse_args()
  214. should_publish = args.upload
  215. version = args.version
  216. path = args.path
  217. start_from = args.start_from
  218. if not path:
  219. path = os.getcwd()
  220. print("Reading package description...")
  221. lines = subprocess.check_output(["swift", "package", "dump-package"])
  222. package_dump = json.loads(lines)
  223. assert(package_dump["name"] == "grpc-swift")
  224. pod_manager = PodManager(path, version, should_publish, package_dump)
  225. pod_manager.go(start_from)
  226. return 0
  227. if __name__ == "__main__":
  228. main()