Browse Source

Update cocoapods and podspec script (#1122)

Motivation:

The podspec script uses Package.resolved to determine dependencies for
the 'GRPC' module as well as version requirements. These version
requirements are wrong per se, but they are overly restrictire.
Additionally, while Package.resolved was previously correct, it no
longer takes into account dependencies resolved for non-product targets
(i.e. swift-argument-parser).

Modifications:

- Build podspec dependency requirements from 'swift package dump-package'
- Replace 'os.system' with 'subprocess.check_call'
- Update pods

Result:

Pod dependency requirementments match Package.swift
George Barnett 4 years ago
parent
commit
6d40ee32dc
4 changed files with 125 additions and 62 deletions
  1. 1 1
      CGRPCZlib.podspec
  2. 1 1
      gRPC-Swift-Plugins.podspec
  3. 8 8
      gRPC-Swift.podspec
  4. 115 52
      scripts/build_podspecs.py

+ 1 - 1
CGRPCZlib.podspec

@@ -2,7 +2,7 @@ Pod::Spec.new do |s|
 
 
     s.name = 'CGRPCZlib'
     s.name = 'CGRPCZlib'
     s.module_name = 'CGRPCZlib'
     s.module_name = 'CGRPCZlib'
-    s.version = '1.0.0-alpha.23'
+    s.version = '1.0.0-alpha.24'
     s.license = { :type => 'Apache 2.0', :file => 'LICENSE' }
     s.license = { :type => 'Apache 2.0', :file => 'LICENSE' }
     s.summary = 'Compression library that provides in-memory compression and decompression functions'
     s.summary = 'Compression library that provides in-memory compression and decompression functions'
     s.homepage = 'https://www.grpc.io'
     s.homepage = 'https://www.grpc.io'

+ 1 - 1
gRPC-Swift-Plugins.podspec

@@ -1,7 +1,7 @@
 Pod::Spec.new do |s|
 Pod::Spec.new do |s|
 
 
     s.name = 'gRPC-Swift-Plugins'
     s.name = 'gRPC-Swift-Plugins'
-    s.version = '1.0.0-alpha.23'
+    s.version = '1.0.0-alpha.24'
     s.license = { :type => 'Apache 2.0', :file => 'LICENSE' }
     s.license = { :type => 'Apache 2.0', :file => 'LICENSE' }
     s.summary = 'Swift gRPC code generator plugin binaries'
     s.summary = 'Swift gRPC code generator plugin binaries'
     s.homepage = 'https://www.grpc.io'
     s.homepage = 'https://www.grpc.io'

+ 8 - 8
gRPC-Swift.podspec

@@ -2,7 +2,7 @@ Pod::Spec.new do |s|
 
 
     s.name = 'gRPC-Swift'
     s.name = 'gRPC-Swift'
     s.module_name = 'GRPC'
     s.module_name = 'GRPC'
-    s.version = '1.0.0-alpha.23'
+    s.version = '1.0.0-alpha.24'
     s.license = { :type => 'Apache 2.0', :file => 'LICENSE' }
     s.license = { :type => 'Apache 2.0', :file => 'LICENSE' }
     s.summary = 'Swift gRPC code generator plugin and runtime library'
     s.summary = 'Swift gRPC code generator plugin and runtime library'
     s.homepage = 'https://www.grpc.io'
     s.homepage = 'https://www.grpc.io'
@@ -17,13 +17,13 @@ Pod::Spec.new do |s|
 
 
     s.source_files = 'Sources/GRPC/**/*.{swift,c,h}'
     s.source_files = 'Sources/GRPC/**/*.{swift,c,h}'
 
 
-    s.dependency 'Logging', '>= 1.4.0', '< 2'
-    s.dependency 'SwiftNIO', '>= 2.25.0', '< 3'
-    s.dependency 'SwiftNIOExtras', '>= 1.7.0', '< 2'
-    s.dependency 'SwiftNIOHTTP2', '>= 1.16.2', '< 2'
-    s.dependency 'SwiftNIOSSL', '>= 2.10.1', '< 3'
-    s.dependency 'SwiftNIOTransportServices', '>= 1.9.1', '< 2'
-    s.dependency 'SwiftProtobuf', '>= 1.14.0', '< 2'
+    s.dependency 'SwiftProtobuf', '>= 1.9.0', '< 2.0.0'
+    s.dependency 'SwiftNIO', '>= 2.22.0', '< 3.0.0'
+    s.dependency 'SwiftNIOTransportServices', '>= 1.6.0', '< 2.0.0'
+    s.dependency 'SwiftNIOSSL', '>= 2.8.0', '< 3.0.0'
+    s.dependency 'SwiftNIOExtras', '>= 1.4.0', '< 2.0.0'
+    s.dependency 'Logging', '>= 1.4.0', '< 2.0.0'
+    s.dependency 'SwiftNIOHTTP2', '>= 1.16.1', '< 2.0.0'
     s.dependency 'CGRPCZlib', s.version.to_s
     s.dependency 'CGRPCZlib', s.version.to_s
 
 
 end
 end

+ 115 - 52
scripts/build_podspecs.py

@@ -19,20 +19,28 @@ import json
 import random
 import random
 import string
 import string
 import argparse
 import argparse
+import subprocess
+import sys
 
 
-class Dependency:
-    def __init__(self, name, version='s.version.to_s', use_verbatim_version=True):
+
+class TargetDependency(object):
+    def __init__(self, name):
         self.name = name
         self.name = name
-        self.version = version
-        self.use_verbatim_version = use_verbatim_version
 
 
-    def as_podspec(self):
-        indent='    '
+    def __str__(self):
+        return "s.dependency '{name}', s.version.to_s".format(name=self.name)
 
 
-        if self.use_verbatim_version:
-            return indent + "s.dependency '%s', %s\n" % (self.name, self.version)
 
 
-        return indent + "s.dependency '%s', '%s'\n" % (self.name, self.version)
+class ProductDependency(object):
+    def __init__(self, name, lower, upper):
+        self.name = name
+        self.lower = lower
+        self.upper = upper
+
+    def __str__(self):
+        return "s.dependency '{name}', '>= {lower}', '< {upper}'".format(
+                name=self.name, lower=self.lower, upper=self.upper)
+
 
 
 class Pod:
 class Pod:
     def __init__(self, name, module_name, version, description, dependencies=None, is_plugins_pod=False):
     def __init__(self, name, module_name, version, description, dependencies=None, is_plugins_pod=False):
@@ -40,16 +48,9 @@ class Pod:
         self.module_name = module_name
         self.module_name = module_name
         self.version = version
         self.version = version
         self.is_plugins_pod = is_plugins_pod
         self.is_plugins_pod = is_plugins_pod
-
-        if dependencies is None:
-            dependencies = []
-
-        self.dependencies = dependencies
+        self.dependencies = dependencies if dependencies is not None else []
         self.description = description
         self.description = description
 
 
-    def add_dependency(self, dependency):
-        self.dependencies.append(dependency)
-
     def as_podspec(self):
     def as_podspec(self):
         print('\n')
         print('\n')
         print('Building Podspec for %s' % self.name)
         print('Building Podspec for %s' % self.name)
@@ -83,16 +84,17 @@ class Pod:
             podspec += "\n" if len(self.dependencies) > 0 else ""
             podspec += "\n" if len(self.dependencies) > 0 else ""
 
 
         for dep in self.dependencies:
         for dep in self.dependencies:
-            podspec += dep.as_podspec()
+            podspec += indent + str(dep) + "\n"
 
 
         podspec += "\nend"
         podspec += "\nend"
         return podspec
         return podspec
 
 
 class PodManager:
 class PodManager:
-    def __init__(self, directory, version, should_publish):
+    def __init__(self, directory, version, should_publish, package_dump):
         self.directory = directory
         self.directory = directory
         self.version = version
         self.version = version
         self.should_publish = should_publish
         self.should_publish = should_publish
+        self.package_dump = package_dump
 
 
     def write(self, pod, contents):
     def write(self, pod, contents):
         print('    Writing to %s/%s.podspec ' % (self.directory, pod))
         print('    Writing to %s/%s.podspec ' % (self.directory, pod))
@@ -100,24 +102,26 @@ class PodManager:
             podspec_file.write(contents)
             podspec_file.write(contents)
 
 
     def publish(self, pod_name):
     def publish(self, pod_name):
-        os.system('pod repo update')
+        subprocess.check_call(['pod', 'repo', 'update'])
         print('    Publishing %s.podspec' % (pod_name))
         print('    Publishing %s.podspec' % (pod_name))
-        os.system('pod trunk push --synchronous %s/%s.podspec' % (self.directory, pod_name))
+        subprocess.check_call(['pod', 'trunk', 'push', '--synchronous',
+                               self.directory + '/' + pod_name])
 
 
     def build_pods(self):
     def build_pods(self):
         cgrpczlib_pod = Pod(
         cgrpczlib_pod = Pod(
-            'CGRPCZlib',
+            self.pod_name_for_grpc_target('CGRPCZlib'),
             'CGRPCZlib',
             'CGRPCZlib',
             self.version,
             self.version,
-            'Compression library that provides in-memory compression and decompression functions'
+            'Compression library that provides in-memory compression and decompression functions',
+            dependencies=self.build_dependency_list('CGRPCZlib')
         )
         )
 
 
         grpc_pod = Pod(
         grpc_pod = Pod(
-            'gRPC-Swift',
+            self.pod_name_for_grpc_target('GRPC'),
             'GRPC',
             'GRPC',
             self.version,
             self.version,
             'Swift gRPC code generator plugin and runtime library',
             'Swift gRPC code generator plugin and runtime library',
-            get_grpc_deps()
+            dependencies=self.build_dependency_list('GRPC')
         )
         )
 
 
         grpc_plugins_pod = Pod(
         grpc_plugins_pod = Pod(
@@ -125,13 +129,10 @@ class PodManager:
             '',
             '',
             self.version,
             self.version,
             'Swift gRPC code generator plugin binaries',
             'Swift gRPC code generator plugin binaries',
-            [],
+            dependencies=[TargetDependency("gRPC-Swift")],
             is_plugins_pod=True
             is_plugins_pod=True
         )
         )
 
 
-        grpc_pod.add_dependency(Dependency(cgrpczlib_pod.name))
-        grpc_plugins_pod.add_dependency(Dependency(grpc_pod.name))
-
         return [cgrpczlib_pod, grpc_pod, grpc_plugins_pod]
         return [cgrpczlib_pod, grpc_pod, grpc_plugins_pod]
 
 
     def go(self, start_from):
     def go(self, start_from):
@@ -148,33 +149,91 @@ class PodManager:
             else:
             else:
                 print('    Skipping Publishing...')
                 print('    Skipping Publishing...')
 
 
-def process_package(string):
-    pod_mappings = {
-        'swift-log': 'Logging',
-        'swift-nio': 'SwiftNIO',
-        'swift-nio-extras': 'SwiftNIOExtras',
-        'swift-nio-http2': 'SwiftNIOHTTP2',
-        'swift-nio-ssl': 'SwiftNIOSSL',
-        'swift-nio-transport-services': 'SwiftNIOTransportServices',
-        'SwiftProtobuf': 'SwiftProtobuf'
-    }
 
 
-    return pod_mappings[string]
+    def pod_name_for_package(self, name):
+        """Return the CocoaPod name for a given Swift package."""
+        pod_mappings = {
+            'swift-log': 'Logging',
+            'swift-nio': 'SwiftNIO',
+            'swift-nio-extras': 'SwiftNIOExtras',
+            'swift-nio-http2': 'SwiftNIOHTTP2',
+            'swift-nio-ssl': 'SwiftNIOSSL',
+            'swift-nio-transport-services': 'SwiftNIOTransportServices',
+            'SwiftProtobuf': 'SwiftProtobuf'
+        }
+        return pod_mappings[name]
+
+
+    def pod_name_for_grpc_target(self, name):
+        """Return the CocoaPod name for a given gRPC Swift target."""
+        return {
+          'GRPC': 'gRPC-Swift',
+          'CGRPCZlib': 'CGRPCZlib'
+        }[name]
 
 
-def get_grpc_deps():
-    with open('Package.resolved') as package:
-        data = json.load(package)
 
 
-    deps = []
+    def get_package_requirements(self, package_name):
+        """
+        Returns the lower and upper bound version requirements for a given
+        package dependency.
+        """
+        for dependency in self.package_dump['dependencies']:
+            if dependency['name'] == package_name:
+                # There should only be 1 range.
+                requirement = dependency['requirement']['range'][0]
+                return (requirement['lowerBound'], requirement['upperBound'])
 
 
-    for obj in data['object']['pins']:
-        package = process_package(obj['package'])
-        version = obj['state']['version']
-        next_major_version = int(version.split('.')[0]) + 1
+        # This shouldn't happen.
+        raise ValueError('Could not find package called', package_name)
 
 
-        deps.append(Dependency(package, '\'>= {}\', \'< {}\''.format(version, next_major_version)))
 
 
-    return deps
+    def get_dependencies(self, target_name):
+        """
+        Returns a tuple of dependency lists for a given target.
+
+        The first entry is the list of product dependencies; dependencies on
+        products from other packages. The second entry is a list of target
+        dependencies, i.e. dependencies on other targets within the package.
+        """
+        for target in self.package_dump['targets']:
+            if target['name'] == target_name:
+                product_dependencies = set()
+                target_dependencies = []
+
+                for dependency in target['dependencies']:
+                    if 'product' in dependency:
+                        product_dependencies.add(dependency['product'][1])
+                    elif 'target' in dependency:
+                        target_dependencies.append(dependency['target'][0])
+                    else:
+                        raise ValueError('Unexpected dependency type:', dependency)
+
+                return (product_dependencies, target_dependencies)
+
+        # This shouldn't happen.
+        raise ValueError('Could not find dependency called', target_name)
+
+
+    def build_dependency_list(self, target_name):
+        """
+        Returns a list of dependencies for the given target.
+
+        Dependencies may be either 'TargetDependency' or 'ProductDependency'.
+        """
+        product, target = self.get_dependencies(target_name)
+        dependencies = []
+
+        for package_name in product:
+            (lower, upper) = self.get_package_requirements(package_name)
+            pod_name = self.pod_name_for_package(package_name)
+            dependencies.append(ProductDependency(pod_name, lower, upper))
+
+        for target_name in target:
+            pod_name = self.pod_name_for_grpc_target(target_name)
+            dependencies.append(TargetDependency(pod_name))
+
+        return dependencies
+
 
 
 def dir_path(path):
 def dir_path(path):
     if os.path.isdir(path):
     if os.path.isdir(path):
@@ -206,7 +265,6 @@ def main():
     )
     )
 
 
     parser.add_argument('version')
     parser.add_argument('version')
-
     args = parser.parse_args()
     args = parser.parse_args()
 
 
     should_publish = args.upload
     should_publish = args.upload
@@ -217,7 +275,12 @@ def main():
     if not path:
     if not path:
         path = os.getcwd()
         path = os.getcwd()
 
 
-    pod_manager = PodManager(path, version, should_publish)
+    print("Reading package description...")
+    lines = subprocess.check_output(["swift", "package", "dump-package"])
+    package_dump = json.loads(lines)
+    assert(package_dump["name"] == "grpc-swift")
+
+    pod_manager = PodManager(path, version, should_publish, package_dump)
     pod_manager.go(start_from)
     pod_manager.go(start_from)
 
 
     return 0
     return 0