| OLD | NEW |
| 1 #!/usr/bin/env python | 1 #!/usr/bin/env python |
| 2 # Copyright (c) 2012 Google Inc. All rights reserved. | 2 # Copyright (c) 2012 Google Inc. All rights reserved. |
| 3 # Use of this source code is governed by a BSD-style license that can be | 3 # Use of this source code is governed by a BSD-style license that can be |
| 4 # found in the LICENSE file. | 4 # found in the LICENSE file. |
| 5 | 5 |
| 6 """Utility functions to perform Xcode-style build steps. | 6 """Utility functions to perform Xcode-style build steps. |
| 7 | 7 |
| 8 These functions are executed via gyp-mac-tool when using the Makefile generator. | 8 These functions are executed via gyp-mac-tool when using the Makefile generator. |
| 9 """ | 9 """ |
| 10 | 10 |
| 11 from __future__ import print_function |
| 12 |
| 11 import fcntl | 13 import fcntl |
| 12 import fnmatch | 14 import fnmatch |
| 13 import glob | 15 import glob |
| 14 import json | 16 import json |
| 15 import os | 17 import os |
| 16 import plistlib | 18 import plistlib |
| 17 import re | 19 import re |
| 18 import shutil | 20 import shutil |
| 19 import string | |
| 20 import struct | 21 import struct |
| 21 import subprocess | 22 import subprocess |
| 22 import sys | 23 import sys |
| 23 import tempfile | 24 import tempfile |
| 24 | 25 |
| 25 | 26 |
| 26 def main(args): | 27 def main(args): |
| 27 executor = MacTool() | 28 executor = MacTool() |
| 28 exit_code = executor.Dispatch(args) | 29 exit_code = executor.Dispatch(args) |
| 29 if exit_code is not None: | 30 if exit_code is not None: |
| (...skipping 113 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 143 def _DetectInputEncoding(self, file_name): | 144 def _DetectInputEncoding(self, file_name): |
| 144 """Reads the first few bytes from file_name and tries to guess the text | 145 """Reads the first few bytes from file_name and tries to guess the text |
| 145 encoding. Returns None as a guess if it can't detect it.""" | 146 encoding. Returns None as a guess if it can't detect it.""" |
| 146 fp = open(file_name, 'rb') | 147 fp = open(file_name, 'rb') |
| 147 try: | 148 try: |
| 148 header = fp.read(3) | 149 header = fp.read(3) |
| 149 except e: | 150 except e: |
| 150 fp.close() | 151 fp.close() |
| 151 return None | 152 return None |
| 152 fp.close() | 153 fp.close() |
| 153 if header.startswith("\xFE\xFF"): | 154 if header.startswith(b"\xFE\xFF"): |
| 154 return "UTF-16" | 155 return "UTF-16" |
| 155 elif header.startswith("\xFF\xFE"): | 156 elif header.startswith(b"\xFF\xFE"): |
| 156 return "UTF-16" | 157 return "UTF-16" |
| 157 elif header.startswith("\xEF\xBB\xBF"): | 158 elif header.startswith(b"\xEF\xBB\xBF"): |
| 158 return "UTF-8" | 159 return "UTF-8" |
| 159 else: | 160 else: |
| 160 return None | 161 return None |
| 161 | 162 |
| 162 def ExecCopyInfoPlist(self, source, dest, convert_to_binary, *keys): | 163 def ExecCopyInfoPlist(self, source, dest, convert_to_binary, *keys): |
| 163 """Copies the |source| Info.plist to the destination directory |dest|.""" | 164 """Copies the |source| Info.plist to the destination directory |dest|.""" |
| 164 # Read the source Info.plist into memory. | 165 # Read the source Info.plist into memory. |
| 165 fd = open(source, 'r') | 166 fd = open(source, 'r') |
| 166 lines = fd.read() | 167 lines = fd.read() |
| 167 fd.close() | 168 fd.close() |
| 168 | 169 |
| 169 # Insert synthesized key/value pairs (e.g. BuildMachineOSBuild). | 170 # Insert synthesized key/value pairs (e.g. BuildMachineOSBuild). |
| 170 plist = plistlib.readPlistFromString(lines) | 171 plist = plistlib.readPlistFromString(lines) |
| 171 if keys: | 172 if keys: |
| 172 plist = dict(plist.items() + json.loads(keys[0]).items()) | 173 plist.update(json.loads(keys[0])) |
| 173 lines = plistlib.writePlistToString(plist) | 174 lines = plistlib.writePlistToString(plist) |
| 174 | 175 |
| 175 # Go through all the environment variables and replace them as variables in | 176 # Go through all the environment variables and replace them as variables in |
| 176 # the file. | 177 # the file. |
| 177 IDENT_RE = re.compile(r'[/\s]') | 178 IDENT_RE = re.compile(r'[/\s]') |
| 178 for key in os.environ: | 179 for key in os.environ: |
| 179 if key.startswith('_'): | 180 if key.startswith('_'): |
| 180 continue | 181 continue |
| 181 evar = '${%s}' % key | 182 evar = '${%s}' % key |
| 182 evalue = os.environ[key] | 183 evalue = os.environ[key] |
| 183 lines = string.replace(lines, evar, evalue) | 184 lines = lines.replace(evar, evalue) |
| 184 | 185 |
| 185 # Xcode supports various suffices on environment variables, which are | 186 # Xcode supports various suffices on environment variables, which are |
| 186 # all undocumented. :rfc1034identifier is used in the standard project | 187 # all undocumented. :rfc1034identifier is used in the standard project |
| 187 # template these days, and :identifier was used earlier. They are used to | 188 # template these days, and :identifier was used earlier. They are used to |
| 188 # convert non-url characters into things that look like valid urls -- | 189 # convert non-url characters into things that look like valid urls -- |
| 189 # except that the replacement character for :identifier, '_' isn't valid | 190 # except that the replacement character for :identifier, '_' isn't valid |
| 190 # in a URL either -- oops, hence :rfc1034identifier was born. | 191 # in a URL either -- oops, hence :rfc1034identifier was born. |
| 191 evar = '${%s:identifier}' % key | 192 evar = '${%s:identifier}' % key |
| 192 evalue = IDENT_RE.sub('_', os.environ[key]) | 193 evalue = IDENT_RE.sub('_', os.environ[key]) |
| 193 lines = string.replace(lines, evar, evalue) | 194 lines = lines.replace(evar, evalue) |
| 194 | 195 |
| 195 evar = '${%s:rfc1034identifier}' % key | 196 evar = '${%s:rfc1034identifier}' % key |
| 196 evalue = IDENT_RE.sub('-', os.environ[key]) | 197 evalue = IDENT_RE.sub('-', os.environ[key]) |
| 197 lines = string.replace(lines, evar, evalue) | 198 lines = lines.replace(evar, evalue) |
| 198 | 199 |
| 199 # Remove any keys with values that haven't been replaced. | 200 # Remove any keys with values that haven't been replaced. |
| 200 lines = lines.split('\n') | 201 lines = lines.split('\n') |
| 201 for i in range(len(lines)): | 202 for i in range(len(lines)): |
| 202 if lines[i].strip().startswith("<string>${"): | 203 if lines[i].strip().startswith("<string>${"): |
| 203 lines[i] = None | 204 lines[i] = None |
| 204 lines[i - 1] = None | 205 lines[i - 1] = None |
| 205 lines = '\n'.join(filter(lambda x: x is not None, lines)) | 206 lines = '\n'.join(filter(lambda x: x is not None, lines)) |
| 206 | 207 |
| 207 # Write out the file with variables replaced. | 208 # Write out the file with variables replaced. |
| (...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 258 env = os.environ.copy() | 259 env = os.environ.copy() |
| 259 # Ref: | 260 # Ref: |
| 260 # http://www.opensource.apple.com/source/cctools/cctools-809/misc/libtool.c | 261 # http://www.opensource.apple.com/source/cctools/cctools-809/misc/libtool.c |
| 261 # The problem with this flag is that it resets the file mtime on the file to | 262 # The problem with this flag is that it resets the file mtime on the file to |
| 262 # epoch=0, e.g. 1970-1-1 or 1969-12-31 depending on timezone. | 263 # epoch=0, e.g. 1970-1-1 or 1969-12-31 depending on timezone. |
| 263 env['ZERO_AR_DATE'] = '1' | 264 env['ZERO_AR_DATE'] = '1' |
| 264 libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE, env=env) | 265 libtoolout = subprocess.Popen(cmd_list, stderr=subprocess.PIPE, env=env) |
| 265 _, err = libtoolout.communicate() | 266 _, err = libtoolout.communicate() |
| 266 for line in err.splitlines(): | 267 for line in err.splitlines(): |
| 267 if not libtool_re.match(line) and not libtool_re5.match(line): | 268 if not libtool_re.match(line) and not libtool_re5.match(line): |
| 268 print >>sys.stderr, line | 269 print(line, file=sys.stderr) |
| 269 # Unconditionally touch the output .a file on the command line if present | 270 # Unconditionally touch the output .a file on the command line if present |
| 270 # and the command succeeded. A bit hacky. | 271 # and the command succeeded. A bit hacky. |
| 271 if not libtoolout.returncode: | 272 if not libtoolout.returncode: |
| 272 for i in range(len(cmd_list) - 1): | 273 for i in range(len(cmd_list) - 1): |
| 273 if cmd_list[i] == "-o" and cmd_list[i+1].endswith('.a'): | 274 if cmd_list[i] == "-o" and cmd_list[i+1].endswith('.a'): |
| 274 os.utime(cmd_list[i+1], None) | 275 os.utime(cmd_list[i+1], None) |
| 275 break | 276 break |
| 276 return libtoolout.returncode | 277 return libtoolout.returncode |
| 277 | 278 |
| 278 def ExecPackageIosFramework(self, framework): | 279 def ExecPackageIosFramework(self, framework): |
| (...skipping 94 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 373 ]) | 374 ]) |
| 374 else: | 375 else: |
| 375 command_line.extend([ | 376 command_line.extend([ |
| 376 '--platform', 'macosx', '--target-device', 'mac', | 377 '--platform', 'macosx', '--target-device', 'mac', |
| 377 '--minimum-deployment-target', os.environ['MACOSX_DEPLOYMENT_TARGET'], | 378 '--minimum-deployment-target', os.environ['MACOSX_DEPLOYMENT_TARGET'], |
| 378 '--compile', | 379 '--compile', |
| 379 os.path.abspath(os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH']), | 380 os.path.abspath(os.environ['UNLOCALIZED_RESOURCES_FOLDER_PATH']), |
| 380 ]) | 381 ]) |
| 381 if keys: | 382 if keys: |
| 382 keys = json.loads(keys) | 383 keys = json.loads(keys) |
| 383 for key, value in keys.iteritems(): | 384 for key, value in keys.items(): |
| 384 arg_name = '--' + key | 385 arg_name = '--' + key |
| 385 if isinstance(value, bool): | 386 if isinstance(value, bool): |
| 386 if value: | 387 if value: |
| 387 command_line.append(arg_name) | 388 command_line.append(arg_name) |
| 388 elif isinstance(value, list): | 389 elif isinstance(value, list): |
| 389 for v in value: | 390 for v in value: |
| 390 command_line.append(arg_name) | 391 command_line.append(arg_name) |
| 391 command_line.append(str(v)) | 392 command_line.append(str(v)) |
| 392 else: | 393 else: |
| 393 command_line.append(arg_name) | 394 command_line.append(arg_name) |
| (...skipping 71 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 465 A tuple of the path to the selected provisioning profile, the data of | 466 A tuple of the path to the selected provisioning profile, the data of |
| 466 the embedded plist in the provisioning profile and the team identifier | 467 the embedded plist in the provisioning profile and the team identifier |
| 467 to use for code signing. | 468 to use for code signing. |
| 468 | 469 |
| 469 Raises: | 470 Raises: |
| 470 SystemExit: if no .mobileprovision can be used to sign the bundle. | 471 SystemExit: if no .mobileprovision can be used to sign the bundle. |
| 471 """ | 472 """ |
| 472 profiles_dir = os.path.join( | 473 profiles_dir = os.path.join( |
| 473 os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles') | 474 os.environ['HOME'], 'Library', 'MobileDevice', 'Provisioning Profiles') |
| 474 if not os.path.isdir(profiles_dir): | 475 if not os.path.isdir(profiles_dir): |
| 475 print >>sys.stderr, ( | 476 print(( |
| 476 'cannot find mobile provisioning for %s' % bundle_identifier) | 477 'cannot find mobile provisioning for %s' % bundle_identifier), |
| 478 file=sys.stderr) |
| 477 sys.exit(1) | 479 sys.exit(1) |
| 478 provisioning_profiles = None | 480 provisioning_profiles = None |
| 479 if profile: | 481 if profile: |
| 480 profile_path = os.path.join(profiles_dir, profile + '.mobileprovision') | 482 profile_path = os.path.join(profiles_dir, profile + '.mobileprovision') |
| 481 if os.path.exists(profile_path): | 483 if os.path.exists(profile_path): |
| 482 provisioning_profiles = [profile_path] | 484 provisioning_profiles = [profile_path] |
| 483 if not provisioning_profiles: | 485 if not provisioning_profiles: |
| 484 provisioning_profiles = glob.glob( | 486 provisioning_profiles = glob.glob( |
| 485 os.path.join(profiles_dir, '*.mobileprovision')) | 487 os.path.join(profiles_dir, '*.mobileprovision')) |
| 486 valid_provisioning_profiles = {} | 488 valid_provisioning_profiles = {} |
| 487 for profile_path in provisioning_profiles: | 489 for profile_path in provisioning_profiles: |
| 488 profile_data = self._LoadProvisioningProfile(profile_path) | 490 profile_data = self._LoadProvisioningProfile(profile_path) |
| 489 app_id_pattern = profile_data.get( | 491 app_id_pattern = profile_data.get( |
| 490 'Entitlements', {}).get('application-identifier', '') | 492 'Entitlements', {}).get('application-identifier', '') |
| 491 for team_identifier in profile_data.get('TeamIdentifier', []): | 493 for team_identifier in profile_data.get('TeamIdentifier', []): |
| 492 app_id = '%s.%s' % (team_identifier, bundle_identifier) | 494 app_id = '%s.%s' % (team_identifier, bundle_identifier) |
| 493 if fnmatch.fnmatch(app_id, app_id_pattern): | 495 if fnmatch.fnmatch(app_id, app_id_pattern): |
| 494 valid_provisioning_profiles[app_id_pattern] = ( | 496 valid_provisioning_profiles[app_id_pattern] = ( |
| 495 profile_path, profile_data, team_identifier) | 497 profile_path, profile_data, team_identifier) |
| 496 if not valid_provisioning_profiles: | 498 if not valid_provisioning_profiles: |
| 497 print >>sys.stderr, ( | 499 print(( |
| 498 'cannot find mobile provisioning for %s' % bundle_identifier) | 500 'cannot find mobile provisioning for %s' % bundle_identifier), |
| 501 file=sys.stderr) |
| 499 sys.exit(1) | 502 sys.exit(1) |
| 500 # If the user has multiple provisioning profiles installed that can be | 503 # If the user has multiple provisioning profiles installed that can be |
| 501 # used for ${bundle_identifier}, pick the most specific one (ie. the | 504 # used for ${bundle_identifier}, pick the most specific one (ie. the |
| 502 # provisioning profile whose pattern is the longest). | 505 # provisioning profile whose pattern is the longest). |
| 503 selected_key = max(valid_provisioning_profiles, key=lambda v: len(v)) | 506 selected_key = max(valid_provisioning_profiles, key=lambda v: len(v)) |
| 504 return valid_provisioning_profiles[selected_key] | 507 return valid_provisioning_profiles[selected_key] |
| 505 | 508 |
| 506 def _LoadProvisioningProfile(self, profile_path): | 509 def _LoadProvisioningProfile(self, profile_path): |
| 507 """Extracts the plist embedded in a provisioning profile. | 510 """Extracts the plist embedded in a provisioning profile. |
| 508 | 511 |
| 509 Args: | 512 Args: |
| 510 profile_path: string, path to the .mobileprovision file | 513 profile_path: string, path to the .mobileprovision file |
| 511 | 514 |
| 512 Returns: | 515 Returns: |
| 513 Content of the plist embedded in the provisioning profile as a dictionary. | 516 Content of the plist embedded in the provisioning profile as a dictionary. |
| 514 """ | 517 """ |
| 515 with tempfile.NamedTemporaryFile() as temp: | 518 with tempfile.NamedTemporaryFile() as temp: |
| 516 subprocess.check_call([ | 519 subprocess.check_call([ |
| 517 'security', 'cms', '-D', '-i', profile_path, '-o', temp.name]) | 520 'security', 'cms', '-D', '-i', profile_path, '-o', temp.name]) |
| 518 return self._LoadPlistMaybeBinary(temp.name) | 521 return self._LoadPlistMaybeBinary(temp.name) |
| 519 | 522 |
| 520 def _MergePlist(self, merged_plist, plist): | 523 def _MergePlist(self, merged_plist, plist): |
| 521 """Merge |plist| into |merged_plist|.""" | 524 """Merge |plist| into |merged_plist|.""" |
| 522 for key, value in plist.iteritems(): | 525 for key, value in plist.items(): |
| 523 if isinstance(value, dict): | 526 if isinstance(value, dict): |
| 524 merged_value = merged_plist.get(key, {}) | 527 merged_value = merged_plist.get(key, {}) |
| 525 if isinstance(merged_value, dict): | 528 if isinstance(merged_value, dict): |
| 526 self._MergePlist(merged_value, value) | 529 self._MergePlist(merged_value, value) |
| 527 merged_plist[key] = merged_value | 530 merged_plist[key] = merged_value |
| 528 else: | 531 else: |
| 529 merged_plist[key] = value | 532 merged_plist[key] = value |
| 530 else: | 533 else: |
| 531 merged_plist[key] = value | 534 merged_plist[key] = value |
| 532 | 535 |
| (...skipping 89 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 622 Args: | 625 Args: |
| 623 data: object, can be either string, list or dictionary | 626 data: object, can be either string, list or dictionary |
| 624 substitutions: dictionary, variable substitutions to perform | 627 substitutions: dictionary, variable substitutions to perform |
| 625 | 628 |
| 626 Returns: | 629 Returns: |
| 627 Copy of data where each references to "$(variable)" has been replaced | 630 Copy of data where each references to "$(variable)" has been replaced |
| 628 by the corresponding value found in substitutions, or left intact if | 631 by the corresponding value found in substitutions, or left intact if |
| 629 the key was not found. | 632 the key was not found. |
| 630 """ | 633 """ |
| 631 if isinstance(data, str): | 634 if isinstance(data, str): |
| 632 for key, value in substitutions.iteritems(): | 635 for key, value in substitutions.items(): |
| 633 data = data.replace('$(%s)' % key, value) | 636 data = data.replace('$(%s)' % key, value) |
| 634 return data | 637 return data |
| 635 if isinstance(data, list): | 638 if isinstance(data, list): |
| 636 return [self._ExpandVariables(v, substitutions) for v in data] | 639 return [self._ExpandVariables(v, substitutions) for v in data] |
| 637 if isinstance(data, dict): | 640 if isinstance(data, dict): |
| 638 return {k: self._ExpandVariables(data[k], substitutions) for k in data} | 641 return {k: self._ExpandVariables(data[k], substitutions) for k in data} |
| 639 return data | 642 return data |
| 640 | 643 |
| 641 def NextGreaterPowerOf2(x): | 644 def NextGreaterPowerOf2(x): |
| 642 return 2**(x-1).bit_length() | 645 return 2**(x-1).bit_length() |
| (...skipping 56 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 699 out.write(struct.pack('<s', '\0')) | 702 out.write(struct.pack('<s', '\0')) |
| 700 base = os.path.dirname(path) + os.sep | 703 base = os.path.dirname(path) + os.sep |
| 701 out.write(struct.pack('<%ds' % len(base), base)) | 704 out.write(struct.pack('<%ds' % len(base), base)) |
| 702 out.write(struct.pack('<s', '\0')) | 705 out.write(struct.pack('<s', '\0')) |
| 703 path = os.path.basename(path) | 706 path = os.path.basename(path) |
| 704 out.write(struct.pack('<%ds' % len(path), path)) | 707 out.write(struct.pack('<%ds' % len(path), path)) |
| 705 out.write(struct.pack('<s', '\0')) | 708 out.write(struct.pack('<s', '\0')) |
| 706 | 709 |
| 707 if __name__ == '__main__': | 710 if __name__ == '__main__': |
| 708 sys.exit(main(sys.argv[1:])) | 711 sys.exit(main(sys.argv[1:])) |
| OLD | NEW |