Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(5)

Side by Side Diff: build/config/ios/codesign.py

Issue 2224553002: Refactor code signing script and template. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: Created 4 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « no previous file | build/config/ios/rules.gni » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
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 fnmatch 6 import fnmatch
7 import glob 7 import glob
8 import os 8 import os
9 import plistlib 9 import plistlib
10 import shutil 10 import shutil
11 import subprocess 11 import subprocess
12 import sys 12 import sys
13 import tempfile 13 import tempfile
14 14
15 15
16 class InstallationError(Exception):
17 """Signals a local installation error that prevents code signing."""
18
19 def __init__(self, fmt, *args):
20 super(Exception, self).__init__(fmt % args)
21
22
23 def GetProvisioningProfilesDir(): 16 def GetProvisioningProfilesDir():
24 """Returns the location of the installed mobile provisioning profiles. 17 """Returns the location of the installed mobile provisioning profiles.
25 18
26 Returns: 19 Returns:
27 The path to the directory containing the installed mobile provisioning 20 The path to the directory containing the installed mobile provisioning
28 profiles as a string. 21 profiles as a string.
29 """ 22 """
30 return os.path.join( 23 return os.path.join(
31 os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles') 24 os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles')
32 25
(...skipping 111 matching lines...) Expand 10 before | Expand all | Expand 10 after
144 137
145 def LoadDefaults(self, defaults): 138 def LoadDefaults(self, defaults):
146 for key, value in defaults.iteritems(): 139 for key, value in defaults.iteritems():
147 if key not in self._data: 140 if key not in self._data:
148 self._data[key] = value 141 self._data[key] = value
149 142
150 def WriteTo(self, target_path): 143 def WriteTo(self, target_path):
151 plistlib.writePlist(self._data, target_path) 144 plistlib.writePlist(self._data, target_path)
152 145
153 146
154 def FindProvisioningProfile(bundle_identifier, provisioning_profile_short_name): 147 def FindProvisioningProfile(bundle_identifier, required):
155 """Finds mobile provisioning profile to use to sign bundle. 148 """Finds mobile provisioning profile to use to sign bundle.
156 149
157 Args: 150 Args:
158 bundle_identifier: the identifier of the bundle to sign. 151 bundle_identifier: the identifier of the bundle to sign.
159 provisioning_profile_short_path: optional short name of the mobile
160 provisioning profile file to use to sign (will still be checked
161 to see if it can sign bundle).
162 152
163 Returns: 153 Returns:
164 The ProvisioningProfile object that can be used to sign the Bundle 154 The ProvisioningProfile object that can be used to sign the Bundle
165 object or None if no matching provisioning profile was found. 155 object or None if no matching provisioning profile was found.
166 """ 156 """
167 provisioning_profiles_dir = GetProvisioningProfilesDir() 157 provisioning_profile_paths = glob.glob(
168 158 os.path.join(GetProvisioningProfilesDir(), '*.mobileprovision'))
169 # First check if there is a mobile provisioning profile installed with
170 # the requested short name. If this is the case, restrict the search to
171 # that mobile provisioning profile, otherwise consider all the installed
172 # mobile provisioning profiles.
173 provisioning_profile_paths = []
174 if provisioning_profile_short_name:
175 provisioning_profile_path = os.path.join(
176 provisioning_profiles_dir,
177 provisioning_profile_short_name + '.mobileprovision')
178 if os.path.isfile(provisioning_profile_path):
179 provisioning_profile_paths.append(provisioning_profile_path)
180
181 if not provisioning_profile_paths:
182 provisioning_profile_paths = glob.glob(
183 os.path.join(provisioning_profiles_dir, '*.mobileprovision'))
184 159
185 # Iterate over all installed mobile provisioning profiles and filter those 160 # Iterate over all installed mobile provisioning profiles and filter those
186 # that can be used to sign the bundle. 161 # that can be used to sign the bundle.
187 valid_provisioning_profiles = [] 162 valid_provisioning_profiles = []
188 for provisioning_profile_path in provisioning_profile_paths: 163 for provisioning_profile_path in provisioning_profile_paths:
189 provisioning_profile = ProvisioningProfile(provisioning_profile_path) 164 provisioning_profile = ProvisioningProfile(provisioning_profile_path)
190 if provisioning_profile.ValidToSignBundle(bundle_identifier): 165 if provisioning_profile.ValidToSignBundle(bundle_identifier):
191 valid_provisioning_profiles.append(provisioning_profile) 166 valid_provisioning_profiles.append(provisioning_profile)
192 167
193 if not valid_provisioning_profiles: 168 if not valid_provisioning_profiles:
194 return None 169 if not required:
170 return None
171 sys.stderr.write(
172 'No mobile provisioning profile found for "%s".\n' %
173 bundle_identifier)
174 sys.exit(1)
Dirk Pranke 2016/08/08 22:15:36 nit: this is minor, but I'd write this as: if req
sdefresne 2016/08/08 22:28:55 Done.
195 175
196 # Select the most specific mobile provisioning profile, i.e. the one with 176 # Select the most specific mobile provisioning profile, i.e. the one with
197 # the longest application identifier pattern. 177 # the longest application identifier pattern.
198 return max( 178 return max(
199 valid_provisioning_profiles, 179 valid_provisioning_profiles,
200 key=lambda p: len(p.application_identifier_pattern)) 180 key=lambda p: len(p.application_identifier_pattern))
201 181
202 182
203 def InstallFramework(framework_path, bundle, args): 183 def CodeSignBundle(bundle_path, identity, extra_args):
184 process = subprocess.Popen(['xcrun', 'codesign', '--force', '--sign',
185 identity, '--timestamp=none'] + list(extra_args) + [bundle_path],
186 stderr=subprocess.PIPE)
187 _, stderr = process.communicate()
188 if process.returncode:
189 sys.stderr.write(stderr)
190 sys.exit(process.returncode)
191 for line in stderr.splitlines():
192 if line.endswith(': replacing existing signature'):
193 # Ignore warning about replacing existing signature as this should only
194 # happen when re-signing system frameworks (and then it is expected).
195 continue
196 sys.stderr.write(line)
197 sys.stderr.write('\n')
198
199
200 def InstallSystemFramework(framework_path, bundle_path, args):
204 """Install framework from |framework_path| to |bundle| and code-re-sign it.""" 201 """Install framework from |framework_path| to |bundle| and code-re-sign it."""
205 installed_framework_path = os.path.join( 202 installed_framework_path = os.path.join(
206 bundle.path, 'Frameworks', os.path.basename(framework_path)) 203 bundle_path, 'Frameworks', os.path.basename(framework_path))
207 204
208 if os.path.exists(installed_framework_path): 205 if os.path.exists(installed_framework_path):
209 shutil.rmtree(installed_framework_path) 206 shutil.rmtree(installed_framework_path)
210 207
211 shutil.copytree(framework_path, installed_framework_path) 208 shutil.copytree(framework_path, installed_framework_path)
212 209 CodeSignBundle(installed_framework_path, args.identity,
213 framework_bundle = Bundle(installed_framework_path) 210 ['--deep', '--preserve-metadata=identifier,entitlements'])
214 CodeSignBundle(framework_bundle.binary_path, framework_bundle, args, True)
215 211
216 212
217 def GenerateEntitlements(path, provisioning_profile, bundle_identifier): 213 def GenerateEntitlements(path, provisioning_profile, bundle_identifier):
218 """Generates an entitlements file. 214 """Generates an entitlements file.
219 215
220 Args: 216 Args:
221 path: path to the entitlements template file 217 path: path to the entitlements template file
222 provisioning_profile: ProvisioningProfile object to use, may be None 218 provisioning_profile: ProvisioningProfile object to use, may be None
223 bundle_identifier: identifier of the bundle to sign. 219 bundle_identifier: identifier of the bundle to sign.
224 """ 220 """
225 entitlements = Entitlements(path) 221 entitlements = Entitlements(path)
226 if provisioning_profile: 222 if provisioning_profile:
227 entitlements.LoadDefaults(provisioning_profile.entitlements) 223 entitlements.LoadDefaults(provisioning_profile.entitlements)
228 app_identifier_prefix = provisioning_profile.team_identifier + '.' 224 app_identifier_prefix = provisioning_profile.team_identifier + '.'
229 else: 225 else:
230 app_identifier_prefix = '*.' 226 app_identifier_prefix = '*.'
231 entitlements.ExpandVariables({ 227 entitlements.ExpandVariables({
232 'CFBundleIdentifier': bundle_identifier, 228 'CFBundleIdentifier': bundle_identifier,
233 'AppIdentifierPrefix': app_identifier_prefix, 229 'AppIdentifierPrefix': app_identifier_prefix,
234 }) 230 })
235 return entitlements 231 return entitlements
236 232
237 233
238 def CodeSignBundle(binary, bundle, args, preserve=False): 234 class Action(object):
239 """Cryptographically signs bundle.
240 235
241 Args: 236 """Class implementing one action supported by the script."""
242 bundle: the Bundle object to sign.
243 args: a dictionary with configuration settings for the code signature,
244 need to define 'entitlements_path', 'provisioning_profile_short_name',
245 'deep_signature' and 'identify' keys.
246 """
247 provisioning_profile = FindProvisioningProfile(
248 bundle.identifier, args.provisioning_profile_short_name)
249 if provisioning_profile:
250 provisioning_profile.Install(bundle)
251 else:
252 sys.stderr.write(
253 'Warning: no mobile provisioning profile found for "%s", some features '
254 'may not be functioning properly.\n' % bundle.identifier)
255 237
256 if preserve: 238 @classmethod
257 process = subprocess.Popen([ 239 def Register(cls, subparsers):
258 'xcrun', 'codesign', '--force', 240 parser = subparsers.add_parser(cls._GetName(), help=cls._GetHelp())
259 '--sign', args.identity if args.identity else '-', 241 parser.set_defaults(func=cls._Execute)
260 '--deep', '--preserve-metadata=identifier,entitlements', 242 cls._Register(parser)
261 '--timestamp=none', bundle.path], stderr=subprocess.PIPE) 243
262 _, stderr = process.communicate() 244
263 if process.returncode: 245 class CodeSignBundleAction(Action):
264 sys.stderr.write(stderr) 246
265 sys.exit(process.returncode) 247 """Class implementing the code-sign-bundle action."""
266 for line in stderr.splitlines(): 248
267 # Ignore expected warning as we are replacing the signature on purpose. 249 @staticmethod
268 if not line.endswith(': replacing existing signature'): 250 def _GetName():
269 sys.stderr.write(line + '\n') 251 return 'code-sign-bundle'
270 else: 252
271 signature_file = os.path.join( 253 @staticmethod
272 bundle.path, '_CodeSignature', 'CodeResources') 254 def _GetHelp():
255 return 'perform code signature for a bundle'
Dirk Pranke 2016/08/08 22:15:36 this would more idiomatically be: name = 'code-
sdefresne 2016/08/08 22:28:55 Done.
256
257 @staticmethod
258 def _Register(parser):
259 parser.add_argument(
260 '--entitlements', '-e', dest='entitlements_path',
261 help='path to the entitlements file to use')
262 parser.add_argument(
263 'path', help='path to the iOS bundle to codesign')
264 parser.add_argument(
265 '--identity', '-i', required=True,
266 help='identity to use to codesign')
267 parser.add_argument(
268 '--binary', '-b', required=True,
269 help='path to the iOS bundle binary')
270 parser.add_argument(
271 '--framework', '-F', action='append', default=[], dest="frameworks",
272 help='install and resign system framework')
273
274 @staticmethod
275 def _Execute(args):
276 if not args.identity:
277 args.identity = '-'
278
279 bundle = Bundle(args.path)
280
281 # Find mobile provisioning profile and embeds it into the bundle (if a code
282 # signing identify has been provided, fails if no valid mobile provisioning
283 # is found).
284 provisioning_profile_required = args.identity != '-'
285 provisioning_profile = FindProvisioningProfile(
286 bundle.identifier, provisioning_profile_required)
287 if provisioning_profile:
288 provisioning_profile.Install(bundle)
289
290 # Delete existing code signature.
291 signature_file = os.path.join(args.path, '_CodeSignature', 'CodeResources')
273 if os.path.isfile(signature_file): 292 if os.path.isfile(signature_file):
274 os.unlink(signature_file) 293 os.unlink(signature_file)
275 294
295 # Install system frameworks if requested.
296 for framework_path in args.frameworks:
297 InstallSystemFramework(framework_path, args.path, args)
298
299 # Copy main binary into bundle.
276 if os.path.isfile(bundle.binary_path): 300 if os.path.isfile(bundle.binary_path):
277 os.unlink(bundle.binary_path) 301 os.unlink(bundle.binary_path)
278 shutil.copy(binary, bundle.binary_path) 302 shutil.copy(args.binary, bundle.binary_path)
279 303
280 codesign_command = [ 304 # Embeds entitlements into the code signature (if code signing identify has
281 'xcrun', 'codesign', '--force', '--sign', 305 # been provided).
282 args.identity if args.identity else '-', 306 codesign_extra_args = []
283 '--timestamp=none', bundle.path, 307 if provisioning_profile:
284 ] 308 temporary_entitlements_file = tempfile.NamedTemporaryFile(suffix='.xcent')
309 codesign_extra_args.extend(
310 ['--entitlements', temporary_entitlements_file.name])
285 311
286 with tempfile.NamedTemporaryFile(suffix='.xcent') as temporary_file_path: 312 entitlements = GenerateEntitlements(
287 if provisioning_profile and args.identity: 313 args.entitlements_path, provisioning_profile, bundle.identifier)
288 entitlements = GenerateEntitlements( 314 entitlements.WriteTo(temporary_entitlements_file.name)
289 args.entitlements_path, provisioning_profile, bundle.identifier) 315
290 entitlements.WriteTo(temporary_file_path.name) 316 CodeSignBundle(bundle.path, args.identity, codesign_extra_args)
291 codesign_command.extend(['--entitlements', temporary_file_path.name])
292 subprocess.check_call(codesign_command)
293 317
294 318
295 def MainCodeSignBundle(args): 319 class GenerateEntitlementsAction(Action):
296 """Adapter to call CodeSignBundle from Main."""
297 bundle = Bundle(args.path)
298 for framework in args.frameworks:
299 InstallFramework(framework, bundle, args)
300 CodeSignBundle(args.binary, bundle, args)
301 320
321 """Class implementing the generate-entitlements action."""
302 322
303 def MainGenerateEntitlements(args): 323 @staticmethod
304 """Adapter to call GenerateEntitlements from Main.""" 324 def _GetName():
305 info_plist = LoadPlistFile(args.info_plist) 325 return 'generate-entitlements'
306 bundle_identifier = info_plist['CFBundleIdentifier']
307 provisioning_profile = FindProvisioningProfile(
308 bundle_identifier, args.provisioning_profile_short_name)
309 326
310 entitlements = GenerateEntitlements( 327 @staticmethod
311 args.entitlements_path, provisioning_profile, bundle_identifier) 328 def _GetHelp():
312 entitlements.WriteTo(args.path) 329 return 'generate entitlements file'
330
331 @staticmethod
332 def _Register(parser):
333 parser.add_argument(
334 '--entitlements', '-e', dest='entitlements_path',
335 help='path to the entitlements file to use')
336 parser.add_argument(
337 'path', help='path to the entitlements file to generate')
338 parser.add_argument(
339 '--info-plist', '-p', required=True,
340 help='path to the bundle Info.plist')
341
342 @staticmethod
343 def _Execute(args):
344 info_plist = LoadPlistFile(args.info_plist)
345 bundle_identifier = info_plist['CFBundleIdentifier']
346 provisioning_profile = FindProvisioningProfile(bundle_identifier, False)
347 entitlements = GenerateEntitlements(
348 args.entitlements_path, provisioning_profile, bundle_identifier)
349 entitlements.WriteTo(args.path)
313 350
314 351
315 def Main(): 352 def Main():
316 parser = argparse.ArgumentParser('codesign iOS bundles') 353 parser = argparse.ArgumentParser('codesign iOS bundles')
317 parser.add_argument(
318 '--provisioning-profile', '-p', dest='provisioning_profile_short_name',
319 help='short name of the mobile provisioning profile to use ('
320 'if undefined, will autodetect the mobile provisioning '
321 'to use)')
322 parser.add_argument(
323 '--entitlements', '-e', dest='entitlements_path',
324 help='path to the entitlements file to use')
325 subparsers = parser.add_subparsers() 354 subparsers = parser.add_subparsers()
326 355
327 code_sign_bundle_parser = subparsers.add_parser( 356 for action in [ CodeSignBundleAction, GenerateEntitlementsAction ]:
328 'code-sign-bundle', 357 action.Register(subparsers)
329 help='code sign a bundle')
330 code_sign_bundle_parser.add_argument(
331 'path', help='path to the iOS bundle to codesign')
332 code_sign_bundle_parser.add_argument(
333 '--identity', '-i', required=True,
334 help='identity to use to codesign')
335 code_sign_bundle_parser.add_argument(
336 '--binary', '-b', required=True,
337 help='path to the iOS bundle binary')
338 code_sign_bundle_parser.add_argument(
339 '--framework', '-F', action='append', default=[], dest="frameworks",
340 help='install and resign system framework')
341 code_sign_bundle_parser.set_defaults(func=MainCodeSignBundle)
342
343 generate_entitlements_parser = subparsers.add_parser(
344 'generate-entitlements',
345 help='generate entitlements file')
346 generate_entitlements_parser.add_argument(
347 'path', help='path to the entitlements file to generate')
348 generate_entitlements_parser.add_argument(
349 '--info-plist', '-p', required=True,
350 help='path to the bundle Info.plist')
351 generate_entitlements_parser.set_defaults(
352 func=MainGenerateEntitlements)
353 358
354 args = parser.parse_args() 359 args = parser.parse_args()
355 args.func(args) 360 args.func(args)
356 361
357 362
358 if __name__ == '__main__': 363 if __name__ == '__main__':
359 sys.exit(Main()) 364 sys.exit(Main())
OLDNEW
« no previous file with comments | « no previous file | build/config/ios/rules.gni » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698