OLD | NEW |
1 # Copyright (c) 2012 Google Inc. All rights reserved. | 1 # Copyright (c) 2012 Google Inc. 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 """ | 5 """ |
6 This module contains classes that help to emulate xcodebuild behavior on top of | 6 This module contains classes that help to emulate xcodebuild behavior on top of |
7 other build systems, such as make and ninja. | 7 other build systems, such as make and ninja. |
8 """ | 8 """ |
9 | 9 |
10 import gyp.common | 10 import gyp.common |
11 import os.path | 11 import os.path |
12 import re | 12 import re |
13 import shlex | 13 import shlex |
14 import subprocess | 14 import subprocess |
15 import sys | 15 import sys |
16 from gyp.common import GypError | 16 from gyp.common import GypError |
17 | 17 |
18 class XcodeSettings(object): | 18 class XcodeSettings(object): |
19 """A class that understands the gyp 'xcode_settings' object.""" | 19 """A class that understands the gyp 'xcode_settings' object.""" |
20 | 20 |
21 # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached | 21 # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached |
22 # at class-level for efficiency. | 22 # at class-level for efficiency. |
23 _sdk_path_cache = {} | 23 _sdk_path_cache = {} |
24 | 24 |
| 25 # Populated lazily by GetExtraPlistItems(). Shared by all XcodeSettings, so |
| 26 # cached at class-level for efficiency. |
| 27 _plist_cache = {} |
| 28 |
25 def __init__(self, spec): | 29 def __init__(self, spec): |
26 self.spec = spec | 30 self.spec = spec |
27 | 31 |
28 self.isIOS = False | 32 self.isIOS = False |
29 | 33 |
30 # Per-target 'xcode_settings' are pushed down into configs earlier by gyp. | 34 # Per-target 'xcode_settings' are pushed down into configs earlier by gyp. |
31 # This means self.xcode_settings[config] always contains all settings | 35 # This means self.xcode_settings[config] always contains all settings |
32 # for that config -- the per-target settings as well. Settings that are | 36 # for that config -- the per-target settings as well. Settings that are |
33 # the same for all configs are implicitly per-target settings. | 37 # the same for all configs are implicitly per-target settings. |
34 self.xcode_settings = {} | 38 self.xcode_settings = {} |
(...skipping 204 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
239 return self._GetBundleBinaryPath() | 243 return self._GetBundleBinaryPath() |
240 else: | 244 else: |
241 return self._GetStandaloneBinaryPath() | 245 return self._GetStandaloneBinaryPath() |
242 | 246 |
243 def GetActiveArchs(self, configname): | 247 def GetActiveArchs(self, configname): |
244 """Returns the architectures this target should be built for.""" | 248 """Returns the architectures this target should be built for.""" |
245 # TODO: Look at VALID_ARCHS, ONLY_ACTIVE_ARCH; possibly set | 249 # TODO: Look at VALID_ARCHS, ONLY_ACTIVE_ARCH; possibly set |
246 # CURRENT_ARCH / NATIVE_ARCH env vars? | 250 # CURRENT_ARCH / NATIVE_ARCH env vars? |
247 return self.xcode_settings[configname].get('ARCHS', ['i386']) | 251 return self.xcode_settings[configname].get('ARCHS', ['i386']) |
248 | 252 |
249 def _GetSdkVersionInfoItem(self, sdk, infoitem): | 253 def _GetStdout(self, cmdlist): |
250 job = subprocess.Popen(['xcodebuild', '-version', '-sdk', sdk, infoitem], | 254 job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE) |
251 stdout=subprocess.PIPE) | |
252 out = job.communicate()[0] | 255 out = job.communicate()[0] |
253 if job.returncode != 0: | 256 if job.returncode != 0: |
254 sys.stderr.write(out + '\n') | 257 sys.stderr.write(out + '\n') |
255 raise GypError('Error %d running xcodebuild' % job.returncode) | 258 raise GypError('Error %d running %s' % (job.returncode, cmdlist[0])) |
256 return out.rstrip('\n') | 259 return out.rstrip('\n') |
257 | 260 |
| 261 def _GetSdkVersionInfoItem(self, sdk, infoitem): |
| 262 return self._GetStdout(['xcodebuild', '-version', '-sdk', sdk, infoitem]) |
| 263 |
| 264 def _SdkRoot(self): |
| 265 return self.GetPerTargetSetting('SDKROOT', default='') |
| 266 |
258 def _SdkPath(self): | 267 def _SdkPath(self): |
259 sdk_root = self.GetPerTargetSetting('SDKROOT', default='macosx') | 268 sdk_root = self._SdkRoot() |
260 if sdk_root.startswith('/'): | 269 if sdk_root.startswith('/'): |
261 return sdk_root | 270 return sdk_root |
262 if sdk_root not in XcodeSettings._sdk_path_cache: | 271 if sdk_root not in XcodeSettings._sdk_path_cache: |
263 XcodeSettings._sdk_path_cache[sdk_root] = self._GetSdkVersionInfoItem( | 272 XcodeSettings._sdk_path_cache[sdk_root] = self._GetSdkVersionInfoItem( |
264 sdk_root, 'Path') | 273 sdk_root, 'Path') |
265 return XcodeSettings._sdk_path_cache[sdk_root] | 274 return XcodeSettings._sdk_path_cache[sdk_root] |
266 | 275 |
267 def _AppendPlatformVersionMinFlags(self, lst): | 276 def _AppendPlatformVersionMinFlags(self, lst): |
268 self._Appendf(lst, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s') | 277 self._Appendf(lst, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s') |
269 if 'IPHONEOS_DEPLOYMENT_TARGET' in self._Settings(): | 278 if 'IPHONEOS_DEPLOYMENT_TARGET' in self._Settings(): |
(...skipping 382 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
652 for key, value in self.xcode_settings[configname].iteritems(): | 661 for key, value in self.xcode_settings[configname].iteritems(): |
653 if key not in result: | 662 if key not in result: |
654 continue | 663 continue |
655 elif result[key] != value: | 664 elif result[key] != value: |
656 del result[key] | 665 del result[key] |
657 return result | 666 return result |
658 | 667 |
659 def GetPerTargetSetting(self, setting, default=None): | 668 def GetPerTargetSetting(self, setting, default=None): |
660 """Tries to get xcode_settings.setting from spec. Assumes that the setting | 669 """Tries to get xcode_settings.setting from spec. Assumes that the setting |
661 has the same value in all configurations and throws otherwise.""" | 670 has the same value in all configurations and throws otherwise.""" |
662 first_pass = True | 671 is_first_pass = True |
663 result = None | 672 result = None |
664 for configname in sorted(self.xcode_settings.keys()): | 673 for configname in sorted(self.xcode_settings.keys()): |
665 if first_pass: | 674 if is_first_pass: |
666 result = self.xcode_settings[configname].get(setting, None) | 675 result = self.xcode_settings[configname].get(setting, None) |
667 first_pass = False | 676 is_first_pass = False |
668 else: | 677 else: |
669 assert result == self.xcode_settings[configname].get(setting, None), ( | 678 assert result == self.xcode_settings[configname].get(setting, None), ( |
670 "Expected per-target setting for '%s', got per-config setting " | 679 "Expected per-target setting for '%s', got per-config setting " |
671 "(target %s)" % (setting, spec['target_name'])) | 680 "(target %s)" % (setting, spec['target_name'])) |
672 if result is None: | 681 if result is None: |
673 return default | 682 return default |
674 return result | 683 return result |
675 | 684 |
676 def _GetStripPostbuilds(self, configname, output_binary, quiet): | 685 def _GetStripPostbuilds(self, configname, output_binary, quiet): |
677 """Returns a list of shell commands that contain the shell commands | 686 """Returns a list of shell commands that contain the shell commands |
(...skipping 67 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
745 l = library | 754 l = library |
746 return l.replace('$(SDKROOT)', self._SdkPath()) | 755 return l.replace('$(SDKROOT)', self._SdkPath()) |
747 | 756 |
748 def AdjustLibraries(self, libraries): | 757 def AdjustLibraries(self, libraries): |
749 """Transforms entries like 'Cocoa.framework' in libraries into entries like | 758 """Transforms entries like 'Cocoa.framework' in libraries into entries like |
750 '-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc. | 759 '-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc. |
751 """ | 760 """ |
752 libraries = [ self._AdjustLibrary(library) for library in libraries] | 761 libraries = [ self._AdjustLibrary(library) for library in libraries] |
753 return libraries | 762 return libraries |
754 | 763 |
| 764 def _BuildMachineOSBuild(self): |
| 765 return self._GetStdout(['sw_vers', '-buildVersion']) |
| 766 |
| 767 def _XcodeVersion(self): |
| 768 # `xcodebuild -version` output looks like |
| 769 # Xcode 4.6.3 |
| 770 # Build version 4H1503 |
| 771 # Convert that to '0463', '4H1503'. |
| 772 version, build = self._GetStdout(['xcodebuild', '-version']).splitlines() |
| 773 # Be careful to convert "4.2" to "0420": |
| 774 version = version.split()[-1].replace('.', '') |
| 775 version = (version + '0' * (3 - len(version))).zfill(4) |
| 776 build = build.split()[-1] |
| 777 return version, build |
| 778 |
| 779 def GetExtraPlistItems(self): |
| 780 """Returns a dictionary with extra items to insert into Info.plist.""" |
| 781 if not XcodeSettings._plist_cache: |
| 782 cache = XcodeSettings._plist_cache |
| 783 cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild() |
| 784 |
| 785 xcode, xcode_build = self._XcodeVersion() |
| 786 cache['DTXcode'] = xcode |
| 787 cache['DTXcodeBuild'] = xcode_build |
| 788 |
| 789 sdk_root = self._SdkRoot() |
| 790 cache['DTSDKName'] = sdk_root |
| 791 if xcode >= '0430': |
| 792 cache['DTSDKBuild'] = self._GetSdkVersionInfoItem( |
| 793 sdk_root, 'ProductBuildVersion') |
| 794 else: |
| 795 cache['DTSDKBuild'] = cache['BuildMachineOSBuild'] |
| 796 |
| 797 return XcodeSettings._plist_cache |
| 798 |
755 | 799 |
756 class MacPrefixHeader(object): | 800 class MacPrefixHeader(object): |
757 """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature. | 801 """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature. |
758 | 802 |
759 This feature consists of several pieces: | 803 This feature consists of several pieces: |
760 * If GCC_PREFIX_HEADER is present, all compilations in that project get an | 804 * If GCC_PREFIX_HEADER is present, all compilations in that project get an |
761 additional |-include path_to_prefix_header| cflag. | 805 additional |-include path_to_prefix_header| cflag. |
762 * If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is | 806 * If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is |
763 instead compiled, and all other compilations in the project get an | 807 instead compiled, and all other compilations in the project get an |
764 additional |-include path_to_compiled_header| instead. | 808 additional |-include path_to_compiled_header| instead. |
(...skipping 359 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
1124 def GetSpecPostbuildCommands(spec, quiet=False): | 1168 def GetSpecPostbuildCommands(spec, quiet=False): |
1125 """Returns the list of postbuilds explicitly defined on |spec|, in a form | 1169 """Returns the list of postbuilds explicitly defined on |spec|, in a form |
1126 executable by a shell.""" | 1170 executable by a shell.""" |
1127 postbuilds = [] | 1171 postbuilds = [] |
1128 for postbuild in spec.get('postbuilds', []): | 1172 for postbuild in spec.get('postbuilds', []): |
1129 if not quiet: | 1173 if not quiet: |
1130 postbuilds.append('echo POSTBUILD\\(%s\\) %s' % ( | 1174 postbuilds.append('echo POSTBUILD\\(%s\\) %s' % ( |
1131 spec['target_name'], postbuild['postbuild_name'])) | 1175 spec['target_name'], postbuild['postbuild_name'])) |
1132 postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action'])) | 1176 postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action'])) |
1133 return postbuilds | 1177 return postbuilds |
OLD | NEW |