Chromium Code Reviews| 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 |