OLD | NEW |
---|---|
1 # Copyright 2016 The Chromium Authors. All rights reserved. | 1 # Copyright 2016 The Chromium Authors. All rights reserved. |
2 # Use of this source code is governed by a BSD-style license that can be | 2 # Use of this source code is governed by a BSD-style license that can be |
3 # found in the LICENSE file. | 3 # found in the LICENSE file. |
4 | 4 |
5 import argparse | 5 import argparse |
6 import datetime | |
6 import fnmatch | 7 import fnmatch |
7 import glob | 8 import glob |
8 import os | 9 import os |
9 import plistlib | 10 import plistlib |
10 import shutil | 11 import shutil |
11 import subprocess | 12 import subprocess |
12 import sys | 13 import sys |
13 import tempfile | 14 import tempfile |
14 | 15 |
15 | 16 |
(...skipping 42 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
58 return os.path.join(self._path, self._data['CFBundleExecutable']) | 59 return os.path.join(self._path, self._data['CFBundleExecutable']) |
59 | 60 |
60 | 61 |
61 class ProvisioningProfile(object): | 62 class ProvisioningProfile(object): |
62 """Wraps a mobile provisioning profile file.""" | 63 """Wraps a mobile provisioning profile file.""" |
63 | 64 |
64 def __init__(self, provisioning_profile_path): | 65 def __init__(self, provisioning_profile_path): |
65 """Initializes the ProvisioningProfile with data from profile file.""" | 66 """Initializes the ProvisioningProfile with data from profile file.""" |
66 self._path = provisioning_profile_path | 67 self._path = provisioning_profile_path |
67 self._data = plistlib.readPlistFromString(subprocess.check_output([ | 68 self._data = plistlib.readPlistFromString(subprocess.check_output([ |
68 'xcrun', 'security', 'cms', '-D', '-i', provisioning_profile_path])) | 69 'xcrun', 'security', 'cms', '-D', '-u', 'certUsageAnyCA', |
70 '-i', provisioning_profile_path])) | |
69 | 71 |
70 @property | 72 @property |
71 def path(self): | 73 def path(self): |
72 return self._path | 74 return self._path |
73 | 75 |
74 @property | 76 @property |
75 def application_identifier_pattern(self): | 77 def application_identifier_pattern(self): |
76 return self._data.get('Entitlements', {}).get('application-identifier', '') | 78 return self._data.get('Entitlements', {}).get('application-identifier', '') |
77 | 79 |
78 @property | 80 @property |
79 def team_identifier(self): | 81 def team_identifier(self): |
80 return self._data.get('TeamIdentifier', [''])[0] | 82 return self._data.get('TeamIdentifier', [''])[0] |
81 | 83 |
82 @property | 84 @property |
83 def entitlements(self): | 85 def entitlements(self): |
84 return self._data.get('Entitlements', {}) | 86 return self._data.get('Entitlements', {}) |
85 | 87 |
88 @property | |
89 def expiration_date(self): | |
90 return self._data.get('ExpirationDate', datetime.datetime.now()) | |
91 | |
86 def ValidToSignBundle(self, bundle_identifier): | 92 def ValidToSignBundle(self, bundle_identifier): |
87 """Checks whether the provisioning profile can sign bundle_identifier. | 93 """Checks whether the provisioning profile can sign bundle_identifier. |
88 | 94 |
89 Args: | 95 Args: |
90 bundle_identifier: the identifier of the bundle that needs to be signed. | 96 bundle_identifier: the identifier of the bundle that needs to be signed. |
91 | 97 |
92 Returns: | 98 Returns: |
93 True if the mobile provisioning profile can be used to sign a bundle | 99 True if the mobile provisioning profile can be used to sign a bundle |
94 with the corresponding bundle_identifier, False otherwise. | 100 with the corresponding bundle_identifier, False otherwise. |
95 """ | 101 """ |
96 return fnmatch.fnmatch( | 102 return fnmatch.fnmatch( |
97 '%s.%s' % (self.team_identifier, bundle_identifier), | 103 '%s.%s' % (self.team_identifier, bundle_identifier), |
98 self.application_identifier_pattern) | 104 self.application_identifier_pattern) |
justincohen
2016/10/25 17:41:51
can application identifier pattern include a star,
sdefresne
2016/10/25 17:54:57
Yes and yes (this is why I'm using fnmatch.fnmatch
| |
99 | 105 |
100 def Install(self, installation_path): | 106 def Install(self, installation_path): |
101 """Copies mobile provisioning profile info to |installation_path|.""" | 107 """Copies mobile provisioning profile info to |installation_path|.""" |
102 shutil.copy2(self.path, installation_path) | 108 shutil.copy2(self.path, installation_path) |
103 | 109 |
104 | 110 |
105 class Entitlements(object): | 111 class Entitlements(object): |
106 """Wraps an Entitlement plist file.""" | 112 """Wraps an Entitlement plist file.""" |
107 | 113 |
108 def __init__(self, entitlements_path): | 114 def __init__(self, entitlements_path): |
(...skipping 41 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
150 bundle_identifier: the identifier of the bundle to sign. | 156 bundle_identifier: the identifier of the bundle to sign. |
151 | 157 |
152 Returns: | 158 Returns: |
153 The ProvisioningProfile object that can be used to sign the Bundle | 159 The ProvisioningProfile object that can be used to sign the Bundle |
154 object or None if no matching provisioning profile was found. | 160 object or None if no matching provisioning profile was found. |
155 """ | 161 """ |
156 provisioning_profile_paths = glob.glob( | 162 provisioning_profile_paths = glob.glob( |
157 os.path.join(GetProvisioningProfilesDir(), '*.mobileprovision')) | 163 os.path.join(GetProvisioningProfilesDir(), '*.mobileprovision')) |
158 | 164 |
159 # Iterate over all installed mobile provisioning profiles and filter those | 165 # Iterate over all installed mobile provisioning profiles and filter those |
160 # that can be used to sign the bundle. | 166 # that can be used to sign the bundle, ignoring expired ones. |
167 now = datetime.datetime.now() | |
161 valid_provisioning_profiles = [] | 168 valid_provisioning_profiles = [] |
169 one_hour = datetime.timedelta(0, 3600) | |
162 for provisioning_profile_path in provisioning_profile_paths: | 170 for provisioning_profile_path in provisioning_profile_paths: |
163 provisioning_profile = ProvisioningProfile(provisioning_profile_path) | 171 provisioning_profile = ProvisioningProfile(provisioning_profile_path) |
172 if provisioning_profile.expiration_date - now < one_hour: | |
173 sys.stderr.write( | |
174 'Warning: ignoring expired provisioning profile: %s.\n' % | |
175 provisioning_profile_path) | |
176 continue | |
164 if provisioning_profile.ValidToSignBundle(bundle_identifier): | 177 if provisioning_profile.ValidToSignBundle(bundle_identifier): |
165 valid_provisioning_profiles.append(provisioning_profile) | 178 valid_provisioning_profiles.append(provisioning_profile) |
166 | 179 |
167 if not valid_provisioning_profiles: | 180 if not valid_provisioning_profiles: |
168 if required: | 181 if required: |
169 sys.stderr.write( | 182 sys.stderr.write( |
170 'No mobile provisioning profile found for "%s".\n' % | 183 'Error: no mobile provisioning profile found for "%s".\n' % |
171 bundle_identifier) | 184 bundle_identifier) |
172 sys.exit(1) | 185 sys.exit(1) |
173 return None | 186 return None |
174 | 187 |
175 # Select the most specific mobile provisioning profile, i.e. the one with | 188 # Select the most specific mobile provisioning profile, i.e. the one with |
176 # the longest application identifier pattern. | 189 # the longest application identifier pattern (prefer the one with the latest |
177 return max( | 190 # expiration date as a secondary criteria). |
191 selected_provisioning_profile = max( | |
178 valid_provisioning_profiles, | 192 valid_provisioning_profiles, |
179 key=lambda p: len(p.application_identifier_pattern)) | 193 key=lambda p: (len(p.application_identifier_pattern), p.expiration_date)) |
194 | |
195 one_week = datetime.timedelta(7) | |
196 if selected_provisioning_profile.expiration_date - now < 2 * one_week: | |
197 sys.stderr.write( | |
198 'Warning: selected provisioning profile will expire soon: %s' % | |
justincohen
2016/10/25 17:41:51
awesome!
sdefresne
2016/10/25 17:54:57
Acknowledged.
| |
199 selected_provisioning_profile.path) | |
200 return selected_provisioning_profile | |
180 | 201 |
181 | 202 |
182 def CodeSignBundle(bundle_path, identity, extra_args): | 203 def CodeSignBundle(bundle_path, identity, extra_args): |
183 process = subprocess.Popen(['xcrun', 'codesign', '--force', '--sign', | 204 process = subprocess.Popen(['xcrun', 'codesign', '--force', '--sign', |
184 identity, '--timestamp=none'] + list(extra_args) + [bundle_path], | 205 identity, '--timestamp=none'] + list(extra_args) + [bundle_path], |
185 stderr=subprocess.PIPE) | 206 stderr=subprocess.PIPE) |
186 _, stderr = process.communicate() | 207 _, stderr = process.communicate() |
187 if process.returncode: | 208 if process.returncode: |
188 sys.stderr.write(stderr) | 209 sys.stderr.write(stderr) |
189 sys.exit(process.returncode) | 210 sys.exit(process.returncode) |
(...skipping 213 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
403 | 424 |
404 for action in actions: | 425 for action in actions: |
405 action.Register(subparsers) | 426 action.Register(subparsers) |
406 | 427 |
407 args = parser.parse_args() | 428 args = parser.parse_args() |
408 args.func(args) | 429 args.func(args) |
409 | 430 |
410 | 431 |
411 if __name__ == '__main__': | 432 if __name__ == '__main__': |
412 sys.exit(Main()) | 433 sys.exit(Main()) |
OLD | NEW |