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

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: Address comments. 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:
169 if required:
170 sys.stderr.write(
171 'No mobile provisioning profile found for "%s".\n' %
172 bundle_identifier)
173 sys.exit(1)
194 return None 174 return None
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.name, help=cls.help)
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 name = 'code-sign-bundle'
268 if not line.endswith(': replacing existing signature'): 250 help = 'perform code signature for a bundle'
269 sys.stderr.write(line + '\n') 251
270 else: 252 @staticmethod
271 signature_file = os.path.join( 253 def _Register(parser):
272 bundle.path, '_CodeSignature', 'CodeResources') 254 parser.add_argument(
255 '--entitlements', '-e', dest='entitlements_path',
256 help='path to the entitlements file to use')
257 parser.add_argument(
258 'path', help='path to the iOS bundle to codesign')
259 parser.add_argument(
260 '--identity', '-i', required=True,
261 help='identity to use to codesign')
262 parser.add_argument(
263 '--binary', '-b', required=True,
264 help='path to the iOS bundle binary')
265 parser.add_argument(
266 '--framework', '-F', action='append', default=[], dest="frameworks",
267 help='install and resign system framework')
268
269 @staticmethod
270 def _Execute(args):
271 if not args.identity:
272 args.identity = '-'
273
274 bundle = Bundle(args.path)
275
276 # Find mobile provisioning profile and embeds it into the bundle (if a code
277 # signing identify has been provided, fails if no valid mobile provisioning
278 # is found).
279 provisioning_profile_required = args.identity != '-'
280 provisioning_profile = FindProvisioningProfile(
281 bundle.identifier, provisioning_profile_required)
282 if provisioning_profile:
283 provisioning_profile.Install(bundle)
284
285 # Delete existing code signature.
286 signature_file = os.path.join(args.path, '_CodeSignature', 'CodeResources')
273 if os.path.isfile(signature_file): 287 if os.path.isfile(signature_file):
274 os.unlink(signature_file) 288 os.unlink(signature_file)
275 289
290 # Install system frameworks if requested.
291 for framework_path in args.frameworks:
292 InstallSystemFramework(framework_path, args.path, args)
293
294 # Copy main binary into bundle.
276 if os.path.isfile(bundle.binary_path): 295 if os.path.isfile(bundle.binary_path):
277 os.unlink(bundle.binary_path) 296 os.unlink(bundle.binary_path)
278 shutil.copy(binary, bundle.binary_path) 297 shutil.copy(args.binary, bundle.binary_path)
279 298
280 codesign_command = [ 299 # Embeds entitlements into the code signature (if code signing identify has
281 'xcrun', 'codesign', '--force', '--sign', 300 # been provided).
282 args.identity if args.identity else '-', 301 codesign_extra_args = []
283 '--timestamp=none', bundle.path, 302 if provisioning_profile:
284 ] 303 temporary_entitlements_file = tempfile.NamedTemporaryFile(suffix='.xcent')
304 codesign_extra_args.extend(
305 ['--entitlements', temporary_entitlements_file.name])
285 306
286 with tempfile.NamedTemporaryFile(suffix='.xcent') as temporary_file_path: 307 entitlements = GenerateEntitlements(
287 if provisioning_profile and args.identity: 308 args.entitlements_path, provisioning_profile, bundle.identifier)
288 entitlements = GenerateEntitlements( 309 entitlements.WriteTo(temporary_entitlements_file.name)
289 args.entitlements_path, provisioning_profile, bundle.identifier) 310
290 entitlements.WriteTo(temporary_file_path.name) 311 CodeSignBundle(bundle.path, args.identity, codesign_extra_args)
291 codesign_command.extend(['--entitlements', temporary_file_path.name])
292 subprocess.check_call(codesign_command)
293 312
294 313
295 def MainCodeSignBundle(args): 314 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 315
316 """Class implementing the generate-entitlements action."""
302 317
303 def MainGenerateEntitlements(args): 318 name = 'generate-entitlements'
304 """Adapter to call GenerateEntitlements from Main.""" 319 help = 'generate entitlements file'
305 info_plist = LoadPlistFile(args.info_plist)
306 bundle_identifier = info_plist['CFBundleIdentifier']
307 provisioning_profile = FindProvisioningProfile(
308 bundle_identifier, args.provisioning_profile_short_name)
309 320
310 entitlements = GenerateEntitlements( 321 @staticmethod
311 args.entitlements_path, provisioning_profile, bundle_identifier) 322 def _Register(parser):
312 entitlements.WriteTo(args.path) 323 parser.add_argument(
324 '--entitlements', '-e', dest='entitlements_path',
325 help='path to the entitlements file to use')
326 parser.add_argument(
327 'path', help='path to the entitlements file to generate')
328 parser.add_argument(
329 '--info-plist', '-p', required=True,
330 help='path to the bundle Info.plist')
331
332 @staticmethod
333 def _Execute(args):
334 info_plist = LoadPlistFile(args.info_plist)
335 bundle_identifier = info_plist['CFBundleIdentifier']
336 provisioning_profile = FindProvisioningProfile(bundle_identifier, False)
337 entitlements = GenerateEntitlements(
338 args.entitlements_path, provisioning_profile, bundle_identifier)
339 entitlements.WriteTo(args.path)
313 340
314 341
315 def Main(): 342 def Main():
316 parser = argparse.ArgumentParser('codesign iOS bundles') 343 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() 344 subparsers = parser.add_subparsers()
326 345
327 code_sign_bundle_parser = subparsers.add_parser( 346 for action in [ CodeSignBundleAction, GenerateEntitlementsAction ]:
328 'code-sign-bundle', 347 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 348
354 args = parser.parse_args() 349 args = parser.parse_args()
355 args.func(args) 350 args.func(args)
356 351
357 352
358 if __name__ == '__main__': 353 if __name__ == '__main__':
359 sys.exit(Main()) 354 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