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 |