1c1ced77af959d11dd80252ab471e89906ea70f09maruel@chromium.org#!/usr/bin/env python 2044fbe1c263aef9b29e85fefca1672d530717fa7thakis@chromium.org# Copyright (c) 2012 Google Inc. All rights reserved. 3a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org# Use of this source code is governed by a BSD-style license that can be 4a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org# found in the LICENSE file. 5a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 6a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org"""Utility functions to perform Xcode-style build steps. 7a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 8a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgThese functions are executed via gyp-mac-tool when using the Makefile generator. 9a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org""" 10a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 11a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgimport fcntl 1234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.orgimport fnmatch 1334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.orgimport glob 14818dd59932d9d046d7d85ed27208ef873426303bjustincohen@chromium.orgimport json 15044fbe1c263aef9b29e85fefca1672d530717fa7thakis@chromium.orgimport os 16a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgimport plistlib 17044fbe1c263aef9b29e85fefca1672d530717fa7thakis@chromium.orgimport re 18a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgimport shutil 19a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgimport string 20a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgimport subprocess 21a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgimport sys 2234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.orgimport tempfile 23a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 24c1ced77af959d11dd80252ab471e89906ea70f09maruel@chromium.org 25a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgdef main(args): 26a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org executor = MacTool() 27835f60a22d4a97810e4012b7fb5ef56c1316d170thakis@chromium.org exit_code = executor.Dispatch(args) 28835f60a22d4a97810e4012b7fb5ef56c1316d170thakis@chromium.org if exit_code is not None: 29835f60a22d4a97810e4012b7fb5ef56c1316d170thakis@chromium.org sys.exit(exit_code) 30a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 31c1ced77af959d11dd80252ab471e89906ea70f09maruel@chromium.org 32a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgclass MacTool(object): 33a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org """This class performs all the Mac tooling steps. The methods can either be 34a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org executed directly, or dispatched from an argument list.""" 35a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 36a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org def Dispatch(self, args): 37a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org """Dispatches a string command to a method.""" 38a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org if len(args) < 1: 39a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org raise Exception("Not enough arguments") 40a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 41a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org method = "Exec%s" % self._CommandifyName(args[0]) 42835f60a22d4a97810e4012b7fb5ef56c1316d170thakis@chromium.org return getattr(self, method)(*args[1:]) 43a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 44a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org def _CommandifyName(self, name_string): 45a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org """Transforms a tool name like copy-info-plist to CopyInfoPlist""" 46a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org return name_string.title().replace('-', '') 47a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 48d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org def ExecCopyBundleResource(self, source, dest): 49d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org """Copies a resource file to the bundle/Resources directory, performing any 50d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org necessary compilation on each resource.""" 51d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org extension = os.path.splitext(source)[1].lower() 52d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org if os.path.isdir(source): 53d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org # Copy tree. 547614842ca70d1cca6192166b10c0565f9aee358athakis@chromium.org # TODO(thakis): This copies file attributes like mtime, while the 557614842ca70d1cca6192166b10c0565f9aee358athakis@chromium.org # single-file branch below doesn't. This should probably be changed to 567614842ca70d1cca6192166b10c0565f9aee358athakis@chromium.org # be consistent with the single-file branch. 57d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org if os.path.exists(dest): 58d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org shutil.rmtree(dest) 59d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org shutil.copytree(source, dest) 60d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org elif extension == '.xib': 61d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org return self._CopyXIBFile(source, dest) 62a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org elif extension == '.storyboard': 63a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org return self._CopyXIBFile(source, dest) 64d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org elif extension == '.strings': 65d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org self._CopyStringsFile(source, dest) 66d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org else: 677614842ca70d1cca6192166b10c0565f9aee358athakis@chromium.org shutil.copy(source, dest) 68d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org 69d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org def _CopyXIBFile(self, source, dest): 70d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org """Compiles a XIB file with ibtool into a binary plist in the bundle.""" 71a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org 72a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org # ibtool sometimes crashes with relative paths. See crbug.com/314728. 73a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org base = os.path.dirname(os.path.realpath(__file__)) 74a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org if os.path.relpath(source): 75a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org source = os.path.join(base, source) 76a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org if os.path.relpath(dest): 77a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org dest = os.path.join(base, dest) 78a8b743aed59832935bd510e6878d8a39d08c4c97justincohen@chromium.org 79cb1c21bb0aa903c094b7c4fe273fb8bf88b7354fmark@chromium.org args = ['xcrun', 'ibtool', '--errors', '--warnings', '--notices', 80cb1c21bb0aa903c094b7c4fe273fb8bf88b7354fmark@chromium.org '--output-format', 'human-readable-text', '--compile', dest, source] 81d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org ibtool_section_re = re.compile(r'/\*.*\*/') 82d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org ibtool_re = re.compile(r'.*note:.*is clipping its content') 83d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org ibtoolout = subprocess.Popen(args, stdout=subprocess.PIPE) 84d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org current_section_header = None 85d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org for line in ibtoolout.stdout: 86d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org if ibtool_section_re.match(line): 87d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org current_section_header = line 88d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org elif not ibtool_re.match(line): 89d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org if current_section_header: 90d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org sys.stdout.write(current_section_header) 91d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org current_section_header = None 92d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org sys.stdout.write(line) 93d8c0d3501bd3b70b651cd79fcf70f15c62844cedthakis@chromium.org return ibtoolout.returncode 94d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org 95d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org def _CopyStringsFile(self, source, dest): 96d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org """Copies a .strings file using iconv to reconvert the input into UTF-16.""" 97d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org input_code = self._DetectInputEncoding(source) or "UTF-8" 98508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org 99508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org # Xcode's CpyCopyStringsFile / builtin-copyStrings seems to call 100508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org # CFPropertyListCreateFromXMLData() behind the scenes; at least it prints 101508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org # CFPropertyListCreateFromXMLData(): Old-style plist parser: missing 102508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org # semicolon in dictionary. 103508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org # on invalid files. Do the same kind of validation. 104508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org import CoreFoundation 1053a479acaceb2157d3fe729db731a353608914731thakis@chromium.org s = open(source, 'rb').read() 106508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org d = CoreFoundation.CFDataCreate(None, s, len(s)) 107508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org _, error = CoreFoundation.CFPropertyListCreateFromXMLData(None, d, 0, None) 108508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org if error: 109508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org return 110508a41bbbe7d80a0e2371f21b0bb6c484fd579aathakis@chromium.org 1113a479acaceb2157d3fe729db731a353608914731thakis@chromium.org fp = open(dest, 'wb') 1123a479acaceb2157d3fe729db731a353608914731thakis@chromium.org fp.write(s.decode(input_code).encode('UTF-16')) 113d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org fp.close() 114d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org 115d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org def _DetectInputEncoding(self, file_name): 116d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org """Reads the first few bytes from file_name and tries to guess the text 117d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org encoding. Returns None as a guess if it can't detect it.""" 118d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org fp = open(file_name, 'rb') 119d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org try: 120d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org header = fp.read(3) 121d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org except e: 122d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org fp.close() 123d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org return None 124d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org fp.close() 125d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org if header.startswith("\xFE\xFF"): 126ca650e4f12ca7f8384534fba090bfde8a736dd6bthakis@chromium.org return "UTF-16" 127d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org elif header.startswith("\xFF\xFE"): 128ca650e4f12ca7f8384534fba090bfde8a736dd6bthakis@chromium.org return "UTF-16" 129d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org elif header.startswith("\xEF\xBB\xBF"): 130d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org return "UTF-8" 131d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org else: 132d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org return None 133a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 134203dfad2aa74a389c60017250fc70cd5b07cff23thakis@chromium.org def ExecCopyInfoPlist(self, source, dest, *keys): 135a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org """Copies the |source| Info.plist to the destination directory |dest|.""" 136a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Read the source Info.plist into memory. 137a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org fd = open(source, 'r') 138a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org lines = fd.read() 139a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org fd.close() 140a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 141203dfad2aa74a389c60017250fc70cd5b07cff23thakis@chromium.org # Insert synthesized key/value pairs (e.g. BuildMachineOSBuild). 142203dfad2aa74a389c60017250fc70cd5b07cff23thakis@chromium.org plist = plistlib.readPlistFromString(lines) 143818dd59932d9d046d7d85ed27208ef873426303bjustincohen@chromium.org if keys: 144818dd59932d9d046d7d85ed27208ef873426303bjustincohen@chromium.org plist = dict(plist.items() + json.loads(keys[0]).items()) 145203dfad2aa74a389c60017250fc70cd5b07cff23thakis@chromium.org lines = plistlib.writePlistToString(plist) 146203dfad2aa74a389c60017250fc70cd5b07cff23thakis@chromium.org 147a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Go through all the environment variables and replace them as variables in 148a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # the file. 149a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org IDENT_RE = re.compile('[/\s]') 150a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org for key in os.environ: 151a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org if key.startswith('_'): 152a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org continue 153a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org evar = '${%s}' % key 154a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org evalue = os.environ[key] 155a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org lines = string.replace(lines, evar, evalue) 156a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org 157a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org # Xcode supports various suffices on environment variables, which are 158a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org # all undocumented. :rfc1034identifier is used in the standard project 159a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org # template these days, and :identifier was used earlier. They are used to 160a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org # convert non-url characters into things that look like valid urls -- 161a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org # except that the replacement character for :identifier, '_' isn't valid 162a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org # in a URL either -- oops, hence :rfc1034identifier was born. 163a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org evar = '${%s:identifier}' % key 164a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org evalue = IDENT_RE.sub('_', os.environ[key]) 165a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org lines = string.replace(lines, evar, evalue) 166a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org 167a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org evar = '${%s:rfc1034identifier}' % key 168a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org evalue = IDENT_RE.sub('-', os.environ[key]) 169a40f2e5ced62fc42e03916641f30841de2ae05cbthakis@chromium.org lines = string.replace(lines, evar, evalue) 170a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 1714eace4f5d8a1f4cdfc2fb1c9452f735926390557thakis@chromium.org # Remove any keys with values that haven't been replaced. 1724eace4f5d8a1f4cdfc2fb1c9452f735926390557thakis@chromium.org lines = lines.split('\n') 1734eace4f5d8a1f4cdfc2fb1c9452f735926390557thakis@chromium.org for i in range(len(lines)): 1744eace4f5d8a1f4cdfc2fb1c9452f735926390557thakis@chromium.org if lines[i].strip().startswith("<string>${"): 1754eace4f5d8a1f4cdfc2fb1c9452f735926390557thakis@chromium.org lines[i] = None 1764eace4f5d8a1f4cdfc2fb1c9452f735926390557thakis@chromium.org lines[i - 1] = None 1774eace4f5d8a1f4cdfc2fb1c9452f735926390557thakis@chromium.org lines = '\n'.join(filter(lambda x: x is not None, lines)) 1784eace4f5d8a1f4cdfc2fb1c9452f735926390557thakis@chromium.org 179a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Write out the file with variables replaced. 180a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org fd = open(dest, 'w') 181a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org fd.write(lines) 182a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org fd.close() 183a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 184a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Now write out PkgInfo file now that the Info.plist file has been 185a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # "compiled". 186a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org self._WritePkgInfo(dest) 187a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 188a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org def _WritePkgInfo(self, info_plist): 189a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org """This writes the PkgInfo file from the data stored in Info.plist.""" 190a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org plist = plistlib.readPlist(info_plist) 191a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org if not plist: 192a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org return 193a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 194284f7cbbbfef4b28ecd0c57420a80e5a7908774drsesek@chromium.org # Only create PkgInfo for executable types. 195284f7cbbbfef4b28ecd0c57420a80e5a7908774drsesek@chromium.org package_type = plist['CFBundlePackageType'] 196284f7cbbbfef4b28ecd0c57420a80e5a7908774drsesek@chromium.org if package_type != 'APPL': 197284f7cbbbfef4b28ecd0c57420a80e5a7908774drsesek@chromium.org return 198284f7cbbbfef4b28ecd0c57420a80e5a7908774drsesek@chromium.org 199a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # The format of PkgInfo is eight characters, representing the bundle type 200284f7cbbbfef4b28ecd0c57420a80e5a7908774drsesek@chromium.org # and bundle signature, each four characters. If that is missing, four 201a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # '?' characters are used instead. 202465477502ef5545ea6e29a8d3035b3ac62cf7c78thakis@chromium.org signature_code = plist.get('CFBundleSignature', '????') 203465477502ef5545ea6e29a8d3035b3ac62cf7c78thakis@chromium.org if len(signature_code) != 4: # Wrong length resets everything, too. 204a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org signature_code = '?' * 4 205a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 206a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org dest = os.path.join(os.path.dirname(info_plist), 'PkgInfo') 207a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org fp = open(dest, 'w') 208a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org fp.write('%s%s' % (package_type, signature_code)) 209a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org fp.close() 210a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 211d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org def ExecFlock(self, lockfile, *cmd_list): 212d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org """Emulates the most basic behavior of Linux's flock(1).""" 213d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org # Rely on exception handling to report errors. 214d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org fd = os.open(lockfile, os.O_RDONLY|os.O_NOCTTY|os.O_CREAT, 0o666) 215d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org fcntl.flock(fd, fcntl.LOCK_EX) 216d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org return subprocess.call(cmd_list) 217d197d57c5cc7d3271acd24a0af4618ea3d03189athakis@chromium.org 218044fbe1c263aef9b29e85fefca1672d530717fa7thakis@chromium.org def ExecFilterLibtool(self, *cmd_list): 2192c2a71a05a4ad76826da3eadd75bd0d14aa954a4thakis@chromium.org """Calls libtool and filters out '/path/to/libtool: file: foo.o has no 2202c2a71a05a4ad76826da3eadd75bd0d14aa954a4thakis@chromium.org symbols'.""" 2212c2a71a05a4ad76826da3eadd75bd0d14aa954a4thakis@chromium.org libtool_re = re.compile(r'^.*libtool: file: .* has no symbols$') 2221a622e23d0c344c55ccac285cab60c2ab991ac9athakis@chromium.org libtool_re5 = re.compile( 2231a622e23d0c344c55ccac285cab60c2ab991ac9athakis@chromium.org r'^.*libtool: warning for library: ' + 2241a622e23d0c344c55ccac285cab60c2ab991ac9athakis@chromium.org r'.* the table of contents is empty ' + 2251a622e23d0c344c55ccac285cab60c2ab991ac9athakis@chromium.org r'\(no object file members in the library define global symbols\)$') 226044fbe1c263aef9b29e85fefca1672d530717fa7thakis@chromium.org libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE) 2275c809bab10c272ab2e34289a2505fc19ccd1da2cthakis@chromium.org _, err = libtoolout.communicate() 2285c809bab10c272ab2e34289a2505fc19ccd1da2cthakis@chromium.org for line in err.splitlines(): 2291a622e23d0c344c55ccac285cab60c2ab991ac9athakis@chromium.org if not libtool_re.match(line) and not libtool_re5.match(line): 2305c809bab10c272ab2e34289a2505fc19ccd1da2cthakis@chromium.org print >>sys.stderr, line 231044fbe1c263aef9b29e85fefca1672d530717fa7thakis@chromium.org return libtoolout.returncode 232044fbe1c263aef9b29e85fefca1672d530717fa7thakis@chromium.org 233a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org def ExecPackageFramework(self, framework, version): 234a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org """Takes a path to Something.framework and the Current version of that and 235a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org sets up all the symlinks.""" 236a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Find the name of the binary based on the part before the ".framework". 237a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org binary = os.path.basename(framework).split('.')[0] 238a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 239a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org CURRENT = 'Current' 240a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org RESOURCES = 'Resources' 241a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org VERSIONS = 'Versions' 242a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 243a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org if not os.path.exists(os.path.join(framework, VERSIONS, version, binary)): 244a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Binary-less frameworks don't seem to contain symlinks (see e.g. 245a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # chromium's out/Debug/org.chromium.Chromium.manifest/ bundle). 246a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org return 247a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 248a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Move into the framework directory to set the symlinks correctly. 249a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org pwd = os.getcwd() 250a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org os.chdir(framework) 251a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 252a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Set up the Current version. 253a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org self._Relink(version, os.path.join(VERSIONS, CURRENT)) 254a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 255a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Set up the root symlinks. 256a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org self._Relink(os.path.join(VERSIONS, CURRENT, binary), binary) 257a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org self._Relink(os.path.join(VERSIONS, CURRENT, RESOURCES), RESOURCES) 258a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 259a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org # Back to where we were before! 260a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org os.chdir(pwd) 261a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 262a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org def _Relink(self, dest, link): 263a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org """Creates a symlink to |dest| named |link|. If |link| already exists, 264a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org it is overwritten.""" 265a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org if os.path.lexists(link): 266a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org os.remove(link) 267a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org os.symlink(dest, link) 268a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org 26934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def ExecCodeSignBundle(self, key, resource_rules, entitlements, provisioning): 27034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Code sign a bundle. 27134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 27234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org This function tries to code sign an iOS bundle, following the same 27334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org algorithm as Xcode: 27434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 1. copy ResourceRules.plist from the user or the SDK into the bundle, 27534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 2. pick the provisioning profile that best match the bundle identifier, 27634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org and copy it into the bundle as embedded.mobileprovision, 27734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 3. copy Entitlements.plist from user or SDK next to the bundle, 27834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 4. code sign the bundle. 27934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 28034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org resource_rules_path = self._InstallResourceRules(resource_rules) 28134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org substitutions, overrides = self._InstallProvisioningProfile( 28234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org provisioning, self._GetCFBundleIdentifier()) 28334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org entitlements_path = self._InstallEntitlements( 28434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org entitlements, substitutions, overrides) 28534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org subprocess.check_call([ 28634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'codesign', '--force', '--sign', key, '--resource-rules', 28734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org resource_rules_path, '--entitlements', entitlements_path, 28834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.path.join( 28934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['TARGET_BUILD_DIR'], 29034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['FULL_PRODUCT_NAME'])]) 29134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 29234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def _InstallResourceRules(self, resource_rules): 29334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Installs ResourceRules.plist from user or SDK into the bundle. 29434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 29534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Args: 29634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org resource_rules: string, optional, path to the ResourceRules.plist file 29734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org to use, default to "${SDKROOT}/ResourceRules.plist" 29834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 29934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Returns: 30034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Path to the copy of ResourceRules.plist into the bundle. 30134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 30234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org source_path = resource_rules 30334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org target_path = os.path.join( 30434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['BUILT_PRODUCTS_DIR'], 30534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['CONTENTS_FOLDER_PATH'], 30634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'ResourceRules.plist') 30734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if not source_path: 30834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org source_path = os.path.join( 30934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['SDKROOT'], 'ResourceRules.plist') 31034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org shutil.copy2(source_path, target_path) 31134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return target_path 31234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 31334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def _InstallProvisioningProfile(self, profile, bundle_identifier): 31434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Installs embedded.mobileprovision into the bundle. 31534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 31634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Args: 31734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org profile: string, optional, short name of the .mobileprovision file 31834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org to use, if empty or the file is missing, the best file installed 31934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org will be used 32034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org bundle_identifier: string, value of CFBundleIdentifier from Info.plist 32134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 32234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Returns: 32334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org A tuple containing two dictionary: variables substitutions and values 32434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org to overrides when generating the entitlements file. 32534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 32634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org source_path, provisioning_data, team_id = self._FindProvisioningProfile( 32734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org profile, bundle_identifier) 32834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org target_path = os.path.join( 32934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['BUILT_PRODUCTS_DIR'], 33034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['CONTENTS_FOLDER_PATH'], 33134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'embedded.mobileprovision') 33234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org shutil.copy2(source_path, target_path) 33334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org substitutions = self._GetSubstitutions(bundle_identifier, team_id + '.') 33434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return substitutions, provisioning_data['Entitlements'] 33534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 33634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def _FindProvisioningProfile(self, profile, bundle_identifier): 33734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Finds the .mobileprovision file to use for signing the bundle. 33834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 33934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Checks all the installed provisioning profiles (or if the user specified 34034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org the PROVISIONING_PROFILE variable, only consult it) and select the most 34134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org specific that correspond to the bundle identifier. 34234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 34334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Args: 34434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org profile: string, optional, short name of the .mobileprovision file 34534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org to use, if empty or the file is missing, the best file installed 34634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org will be used 34734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org bundle_identifier: string, value of CFBundleIdentifier from Info.plist 34834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 34934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Returns: 35034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org A tuple of the path to the selected provisioning profile, the data of 35134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org the embedded plist in the provisioning profile and the team identifier 35234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org to use for code signing. 35334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 35434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Raises: 35534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org SystemExit: if no .mobileprovision can be used to sign the bundle. 35634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 35734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org profiles_dir = os.path.join( 35834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles') 35934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if not os.path.isdir(profiles_dir): 36034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org print >>sys.stderr, ( 36134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'cannot find mobile provisioning for %s' % bundle_identifier) 36234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org sys.exit(1) 36334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org provisioning_profiles = None 36434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if profile: 36534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org profile_path = os.path.join(profiles_dir, profile + '.mobileprovision') 36634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if os.path.exists(profile_path): 36734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org provisioning_profiles = [profile_path] 36834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if not provisioning_profiles: 36934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org provisioning_profiles = glob.glob( 37034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.path.join(profiles_dir, '*.mobileprovision')) 37134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org valid_provisioning_profiles = {} 37234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org for profile_path in provisioning_profiles: 37334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org profile_data = self._LoadProvisioningProfile(profile_path) 37434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org app_id_pattern = profile_data.get( 37534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'Entitlements', {}).get('application-identifier', '') 37634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org for team_identifier in profile_data.get('TeamIdentifier', []): 37734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org app_id = '%s.%s' % (team_identifier, bundle_identifier) 37834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if fnmatch.fnmatch(app_id, app_id_pattern): 37934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org valid_provisioning_profiles[app_id_pattern] = ( 38034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org profile_path, profile_data, team_identifier) 38134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if not valid_provisioning_profiles: 38234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org print >>sys.stderr, ( 38334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'cannot find mobile provisioning for %s' % bundle_identifier) 38434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org sys.exit(1) 38534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org # If the user has multiple provisioning profiles installed that can be 38634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org # used for ${bundle_identifier}, pick the most specific one (ie. the 38734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org # provisioning profile whose pattern is the longest). 38834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org selected_key = max(valid_provisioning_profiles, key=lambda v: len(v)) 38934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return valid_provisioning_profiles[selected_key] 39034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 39134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def _LoadProvisioningProfile(self, profile_path): 39234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Extracts the plist embedded in a provisioning profile. 39334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 39434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Args: 39534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org profile_path: string, path to the .mobileprovision file 39634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 39734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Returns: 39834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Content of the plist embedded in the provisioning profile as a dictionary. 39934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 40034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org with tempfile.NamedTemporaryFile() as temp: 40134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org subprocess.check_call([ 40234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'security', 'cms', '-D', '-i', profile_path, '-o', temp.name]) 40334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return self._LoadPlistMaybeBinary(temp.name) 40434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 40534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def _LoadPlistMaybeBinary(self, plist_path): 40634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Loads into a memory a plist possibly encoded in binary format. 40734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 40834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org This is a wrapper around plistlib.readPlist that tries to convert the 40934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org plist to the XML format if it can't be parsed (assuming that it is in 41034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org the binary format). 41134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 41234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Args: 41334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org plist_path: string, path to a plist file, in XML or binary format 41434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 41534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Returns: 41634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Content of the plist as a dictionary. 41734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 41834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org try: 41934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org # First, try to read the file using plistlib that only supports XML, 42034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org # and if an exception is raised, convert a temporary copy to XML and 42134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org # load that copy. 42234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return plistlib.readPlist(plist_path) 42334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org except: 42434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org pass 42534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org with tempfile.NamedTemporaryFile() as temp: 42634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org shutil.copy2(plist_path, temp.name) 42734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org subprocess.check_call(['plutil', '-convert', 'xml1', temp.name]) 42834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return plistlib.readPlist(temp.name) 42934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 43034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def _GetSubstitutions(self, bundle_identifier, app_identifier_prefix): 43134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Constructs a dictionary of variable substitutions for Entitlements.plist. 43234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 43334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Args: 43434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org bundle_identifier: string, value of CFBundleIdentifier from Info.plist 43534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org app_identifier_prefix: string, value for AppIdentifierPrefix 43634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 43734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Returns: 43834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Dictionary of substitutions to apply when generating Entitlements.plist. 43934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 44034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return { 44134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'CFBundleIdentifier': bundle_identifier, 44234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'AppIdentifierPrefix': app_identifier_prefix, 44334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org } 44434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 44534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def _GetCFBundleIdentifier(self): 44634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Extracts CFBundleIdentifier value from Info.plist in the bundle. 44734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 44834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Returns: 44934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Value of CFBundleIdentifier in the Info.plist located in the bundle. 45034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 45134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org info_plist_path = os.path.join( 45234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['TARGET_BUILD_DIR'], 45334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['INFOPLIST_PATH']) 45434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org info_plist_data = self._LoadPlistMaybeBinary(info_plist_path) 45534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return info_plist_data['CFBundleIdentifier'] 45634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 45734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def _InstallEntitlements(self, entitlements, substitutions, overrides): 45834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Generates and install the ${BundleName}.xcent entitlements file. 45934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 46034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Expands variables "$(variable)" pattern in the source entitlements file, 46134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org add extra entitlements defined in the .mobileprovision file and the copy 46234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org the generated plist to "${BundlePath}.xcent". 46334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 46434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Args: 46534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org entitlements: string, optional, path to the Entitlements.plist template 46634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org to use, defaults to "${SDKROOT}/Entitlements.plist" 46734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org substitutions: dictionary, variable substitutions 46834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org overrides: dictionary, values to add to the entitlements 46934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 47034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Returns: 47134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Path to the generated entitlements file. 47234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 47334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org source_path = entitlements 47434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org target_path = os.path.join( 47534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['BUILT_PRODUCTS_DIR'], 47634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['PRODUCT_NAME'] + '.xcent') 47734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if not source_path: 47834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org source_path = os.path.join( 47934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org os.environ['SDKROOT'], 48034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 'Entitlements.plist') 48134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org shutil.copy2(source_path, target_path) 48234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org data = self._LoadPlistMaybeBinary(target_path) 48334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org data = self._ExpandVariables(data, substitutions) 48434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if overrides: 48534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org for key in overrides: 48634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if key not in data: 48734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org data[key] = overrides[key] 48834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org plistlib.writePlist(data, target_path) 48934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return target_path 49034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 49134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org def _ExpandVariables(self, data, substitutions): 49234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """Expands variables "$(variable)" in data. 49334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 49434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Args: 49534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org data: object, can be either string, list or dictionary 49634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org substitutions: dictionary, variable substitutions to perform 49734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org 49834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Returns: 49934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org Copy of data where each references to "$(variable)" has been replaced 50034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org by the corresponding value found in substitutions, or left intact if 50134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org the key was not found. 50234b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org """ 50334b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if isinstance(data, str): 50434b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org for key, value in substitutions.iteritems(): 50534b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org data = data.replace('$(%s)' % key, value) 50634b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return data 50734b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if isinstance(data, list): 50834b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return [self._ExpandVariables(v, substitutions) for v in data] 50934b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org if isinstance(data, dict): 51034b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return {k: self._ExpandVariables(data[k], substitutions) for k in data} 51134b060532e7753b3de7d33e9d330a8cd0179b5cbjustincohen@chromium.org return data 512c1ced77af959d11dd80252ab471e89906ea70f09maruel@chromium.org 513a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.orgif __name__ == '__main__': 514a803276e6659908e3dc4ad1f5ae7987755fffd50thakis@chromium.org sys.exit(main(sys.argv[1:])) 515