blob: 048511a2690366bc2480b47062582687d82b7a83 [file] [log] [blame]
Ben Murdoch097c5b22016-05-18 11:27:45 +01001# Copyright (c) 2012 Google Inc. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5"""
6This module contains classes that help to emulate xcodebuild behavior on top of
7other build systems, such as make and ninja.
8"""
9
10import copy
11import gyp.common
12import os
13import os.path
14import re
15import shlex
16import subprocess
17import sys
18import tempfile
19from gyp.common import GypError
20
21# Populated lazily by XcodeVersion, for efficiency, and to fix an issue when
22# "xcodebuild" is called too quickly (it has been found to return incorrect
23# version number).
24XCODE_VERSION_CACHE = None
25
26# Populated lazily by GetXcodeArchsDefault, to an |XcodeArchsDefault| instance
27# corresponding to the installed version of Xcode.
28XCODE_ARCHS_DEFAULT_CACHE = None
29
30
31def XcodeArchsVariableMapping(archs, archs_including_64_bit=None):
32 """Constructs a dictionary with expansion for $(ARCHS_STANDARD) variable,
33 and optionally for $(ARCHS_STANDARD_INCLUDING_64_BIT)."""
34 mapping = {'$(ARCHS_STANDARD)': archs}
35 if archs_including_64_bit:
36 mapping['$(ARCHS_STANDARD_INCLUDING_64_BIT)'] = archs_including_64_bit
37 return mapping
38
39class XcodeArchsDefault(object):
40 """A class to resolve ARCHS variable from xcode_settings, resolving Xcode
41 macros and implementing filtering by VALID_ARCHS. The expansion of macros
42 depends on the SDKROOT used ("macosx", "iphoneos", "iphonesimulator") and
43 on the version of Xcode.
44 """
45
46 # Match variable like $(ARCHS_STANDARD).
47 variable_pattern = re.compile(r'\$\([a-zA-Z_][a-zA-Z0-9_]*\)$')
48
49 def __init__(self, default, mac, iphonesimulator, iphoneos):
50 self._default = (default,)
51 self._archs = {'mac': mac, 'ios': iphoneos, 'iossim': iphonesimulator}
52
53 def _VariableMapping(self, sdkroot):
54 """Returns the dictionary of variable mapping depending on the SDKROOT."""
55 sdkroot = sdkroot.lower()
56 if 'iphoneos' in sdkroot:
57 return self._archs['ios']
58 elif 'iphonesimulator' in sdkroot:
59 return self._archs['iossim']
60 else:
61 return self._archs['mac']
62
63 def _ExpandArchs(self, archs, sdkroot):
64 """Expands variables references in ARCHS, and remove duplicates."""
65 variable_mapping = self._VariableMapping(sdkroot)
66 expanded_archs = []
67 for arch in archs:
68 if self.variable_pattern.match(arch):
69 variable = arch
70 try:
71 variable_expansion = variable_mapping[variable]
72 for arch in variable_expansion:
73 if arch not in expanded_archs:
74 expanded_archs.append(arch)
75 except KeyError as e:
76 print 'Warning: Ignoring unsupported variable "%s".' % variable
77 elif arch not in expanded_archs:
78 expanded_archs.append(arch)
79 return expanded_archs
80
81 def ActiveArchs(self, archs, valid_archs, sdkroot):
82 """Expands variables references in ARCHS, and filter by VALID_ARCHS if it
83 is defined (if not set, Xcode accept any value in ARCHS, otherwise, only
84 values present in VALID_ARCHS are kept)."""
85 expanded_archs = self._ExpandArchs(archs or self._default, sdkroot or '')
86 if valid_archs:
87 filtered_archs = []
88 for arch in expanded_archs:
89 if arch in valid_archs:
90 filtered_archs.append(arch)
91 expanded_archs = filtered_archs
92 return expanded_archs
93
94
95def GetXcodeArchsDefault():
96 """Returns the |XcodeArchsDefault| object to use to expand ARCHS for the
97 installed version of Xcode. The default values used by Xcode for ARCHS
98 and the expansion of the variables depends on the version of Xcode used.
99
100 For all version anterior to Xcode 5.0 or posterior to Xcode 5.1 included
101 uses $(ARCHS_STANDARD) if ARCHS is unset, while Xcode 5.0 to 5.0.2 uses
102 $(ARCHS_STANDARD_INCLUDING_64_BIT). This variable was added to Xcode 5.0
103 and deprecated with Xcode 5.1.
104
105 For "macosx" SDKROOT, all version starting with Xcode 5.0 includes 64-bit
106 architecture as part of $(ARCHS_STANDARD) and default to only building it.
107
108 For "iphoneos" and "iphonesimulator" SDKROOT, 64-bit architectures are part
109 of $(ARCHS_STANDARD_INCLUDING_64_BIT) from Xcode 5.0. From Xcode 5.1, they
110 are also part of $(ARCHS_STANDARD).
111
112 All thoses rules are coded in the construction of the |XcodeArchsDefault|
113 object to use depending on the version of Xcode detected. The object is
114 for performance reason."""
115 global XCODE_ARCHS_DEFAULT_CACHE
116 if XCODE_ARCHS_DEFAULT_CACHE:
117 return XCODE_ARCHS_DEFAULT_CACHE
118 xcode_version, _ = XcodeVersion()
119 if xcode_version < '0500':
120 XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
121 '$(ARCHS_STANDARD)',
122 XcodeArchsVariableMapping(['i386']),
123 XcodeArchsVariableMapping(['i386']),
124 XcodeArchsVariableMapping(['armv7']))
125 elif xcode_version < '0510':
126 XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
127 '$(ARCHS_STANDARD_INCLUDING_64_BIT)',
128 XcodeArchsVariableMapping(['x86_64'], ['x86_64']),
129 XcodeArchsVariableMapping(['i386'], ['i386', 'x86_64']),
130 XcodeArchsVariableMapping(
131 ['armv7', 'armv7s'],
132 ['armv7', 'armv7s', 'arm64']))
133 else:
134 XCODE_ARCHS_DEFAULT_CACHE = XcodeArchsDefault(
135 '$(ARCHS_STANDARD)',
136 XcodeArchsVariableMapping(['x86_64'], ['x86_64']),
137 XcodeArchsVariableMapping(['i386', 'x86_64'], ['i386', 'x86_64']),
138 XcodeArchsVariableMapping(
139 ['armv7', 'armv7s', 'arm64'],
140 ['armv7', 'armv7s', 'arm64']))
141 return XCODE_ARCHS_DEFAULT_CACHE
142
143
144class XcodeSettings(object):
145 """A class that understands the gyp 'xcode_settings' object."""
146
147 # Populated lazily by _SdkPath(). Shared by all XcodeSettings, so cached
148 # at class-level for efficiency.
149 _sdk_path_cache = {}
150 _platform_path_cache = {}
151 _sdk_root_cache = {}
152
153 # Populated lazily by GetExtraPlistItems(). Shared by all XcodeSettings, so
154 # cached at class-level for efficiency.
155 _plist_cache = {}
156
157 # Populated lazily by GetIOSPostbuilds. Shared by all XcodeSettings, so
158 # cached at class-level for efficiency.
159 _codesigning_key_cache = {}
160
161 def __init__(self, spec):
162 self.spec = spec
163
164 self.isIOS = False
165 self.mac_toolchain_dir = None
166 self.header_map_path = None
167
168 # Per-target 'xcode_settings' are pushed down into configs earlier by gyp.
169 # This means self.xcode_settings[config] always contains all settings
170 # for that config -- the per-target settings as well. Settings that are
171 # the same for all configs are implicitly per-target settings.
172 self.xcode_settings = {}
173 configs = spec['configurations']
174 for configname, config in configs.iteritems():
175 self.xcode_settings[configname] = config.get('xcode_settings', {})
176 self._ConvertConditionalKeys(configname)
177 if self.xcode_settings[configname].get('IPHONEOS_DEPLOYMENT_TARGET',
178 None):
179 self.isIOS = True
180
181 # This is only non-None temporarily during the execution of some methods.
182 self.configname = None
183
184 # Used by _AdjustLibrary to match .a and .dylib entries in libraries.
185 self.library_re = re.compile(r'^lib([^/]+)\.(a|dylib)$')
186
187 def _ConvertConditionalKeys(self, configname):
188 """Converts or warns on conditional keys. Xcode supports conditional keys,
189 such as CODE_SIGN_IDENTITY[sdk=iphoneos*]. This is a partial implementation
190 with some keys converted while the rest force a warning."""
191 settings = self.xcode_settings[configname]
192 conditional_keys = [key for key in settings if key.endswith(']')]
193 for key in conditional_keys:
194 # If you need more, speak up at http://crbug.com/122592
195 if key.endswith("[sdk=iphoneos*]"):
196 if configname.endswith("iphoneos"):
197 new_key = key.split("[")[0]
198 settings[new_key] = settings[key]
199 else:
200 print 'Warning: Conditional keys not implemented, ignoring:', \
201 ' '.join(conditional_keys)
202 del settings[key]
203
204 def _Settings(self):
205 assert self.configname
206 return self.xcode_settings[self.configname]
207
208 def _Test(self, test_key, cond_key, default):
209 return self._Settings().get(test_key, default) == cond_key
210
211 def _Appendf(self, lst, test_key, format_str, default=None):
212 if test_key in self._Settings():
213 lst.append(format_str % str(self._Settings()[test_key]))
214 elif default:
215 lst.append(format_str % str(default))
216
217 def _WarnUnimplemented(self, test_key):
218 if test_key in self._Settings():
219 print 'Warning: Ignoring not yet implemented key "%s".' % test_key
220
221 def IsBinaryOutputFormat(self, configname):
222 default = "binary" if self.isIOS else "xml"
223 format = self.xcode_settings[configname].get('INFOPLIST_OUTPUT_FORMAT',
224 default)
225 return format == "binary"
226
227 def IsIosFramework(self):
228 return self.spec['type'] == 'shared_library' and self._IsBundle() and \
229 self.isIOS
230
231 def _IsBundle(self):
232 return int(self.spec.get('mac_bundle', 0)) != 0 or self._IsXCTest() or \
233 self._IsXCUiTest()
234
235 def _IsXCTest(self):
236 return int(self.spec.get('mac_xctest_bundle', 0)) != 0
237
238 def _IsXCUiTest(self):
239 return int(self.spec.get('mac_xcuitest_bundle', 0)) != 0
240
241 def _IsIosAppExtension(self):
242 return int(self.spec.get('ios_app_extension', 0)) != 0
243
244 def _IsIosWatchKitExtension(self):
245 return int(self.spec.get('ios_watchkit_extension', 0)) != 0
246
247 def _IsIosWatchApp(self):
248 return int(self.spec.get('ios_watch_app', 0)) != 0
249
250 def GetFrameworkVersion(self):
251 """Returns the framework version of the current target. Only valid for
252 bundles."""
253 assert self._IsBundle()
254 return self.GetPerTargetSetting('FRAMEWORK_VERSION', default='A')
255
256 def GetWrapperExtension(self):
257 """Returns the bundle extension (.app, .framework, .plugin, etc). Only
258 valid for bundles."""
259 assert self._IsBundle()
260 if self.spec['type'] in ('loadable_module', 'shared_library'):
261 default_wrapper_extension = {
262 'loadable_module': 'bundle',
263 'shared_library': 'framework',
264 }[self.spec['type']]
265 wrapper_extension = self.GetPerTargetSetting(
266 'WRAPPER_EXTENSION', default=default_wrapper_extension)
267 return '.' + self.spec.get('product_extension', wrapper_extension)
268 elif self.spec['type'] == 'executable':
269 if self._IsIosAppExtension() or self._IsIosWatchKitExtension():
270 return '.' + self.spec.get('product_extension', 'appex')
271 else:
272 return '.' + self.spec.get('product_extension', 'app')
273 else:
274 assert False, "Don't know extension for '%s', target '%s'" % (
275 self.spec['type'], self.spec['target_name'])
276
277 def GetProductName(self):
278 """Returns PRODUCT_NAME."""
279 return self.spec.get('product_name', self.spec['target_name'])
280
281 def GetFullProductName(self):
282 """Returns FULL_PRODUCT_NAME."""
283 if self._IsBundle():
284 return self.GetWrapperName()
285 else:
286 return self._GetStandaloneBinaryPath()
287
288 def GetWrapperName(self):
289 """Returns the directory name of the bundle represented by this target.
290 Only valid for bundles."""
291 assert self._IsBundle()
292 return self.GetProductName() + self.GetWrapperExtension()
293
294 def GetBundleContentsFolderPath(self):
295 """Returns the qualified path to the bundle's contents folder. E.g.
296 Chromium.app/Contents or Foo.bundle/Versions/A. Only valid for bundles."""
297 if self.isIOS:
298 return self.GetWrapperName()
299 assert self._IsBundle()
300 if self.spec['type'] == 'shared_library':
301 return os.path.join(
302 self.GetWrapperName(), 'Versions', self.GetFrameworkVersion())
303 else:
304 # loadable_modules have a 'Contents' folder like executables.
305 return os.path.join(self.GetWrapperName(), 'Contents')
306
307 def GetBundleResourceFolder(self):
308 """Returns the qualified path to the bundle's resource folder. E.g.
309 Chromium.app/Contents/Resources. Only valid for bundles."""
310 assert self._IsBundle()
311 if self.isIOS:
312 return self.GetBundleContentsFolderPath()
313 return os.path.join(self.GetBundleContentsFolderPath(), 'Resources')
314
315 def GetBundlePlistPath(self):
316 """Returns the qualified path to the bundle's plist file. E.g.
317 Chromium.app/Contents/Info.plist. Only valid for bundles."""
318 assert self._IsBundle()
319 if self.spec['type'] in ('executable', 'loadable_module') or \
320 self.IsIosFramework():
321 return os.path.join(self.GetBundleContentsFolderPath(), 'Info.plist')
322 else:
323 return os.path.join(self.GetBundleContentsFolderPath(),
324 'Resources', 'Info.plist')
325
326 def GetProductType(self):
327 """Returns the PRODUCT_TYPE of this target."""
328 if self._IsIosAppExtension():
329 assert self._IsBundle(), ('ios_app_extension flag requires mac_bundle '
330 '(target %s)' % self.spec['target_name'])
331 return 'com.apple.product-type.app-extension'
332 if self._IsIosWatchKitExtension():
333 assert self._IsBundle(), ('ios_watchkit_extension flag requires '
334 'mac_bundle (target %s)' % self.spec['target_name'])
335 return 'com.apple.product-type.watchkit-extension'
336 if self._IsIosWatchApp():
337 assert self._IsBundle(), ('ios_watch_app flag requires mac_bundle '
338 '(target %s)' % self.spec['target_name'])
339 return 'com.apple.product-type.application.watchapp'
340 if self._IsXCUiTest():
341 assert self._IsBundle(), ('mac_xcuitest_bundle flag requires mac_bundle '
342 '(target %s)' % self.spec['target_name'])
343 return 'com.apple.product-type.bundle.ui-testing'
344 if self._IsBundle():
345 return {
346 'executable': 'com.apple.product-type.application',
347 'loadable_module': 'com.apple.product-type.bundle',
348 'shared_library': 'com.apple.product-type.framework',
349 }[self.spec['type']]
350 else:
351 return {
352 'executable': 'com.apple.product-type.tool',
353 'loadable_module': 'com.apple.product-type.library.dynamic',
354 'shared_library': 'com.apple.product-type.library.dynamic',
355 'static_library': 'com.apple.product-type.library.static',
356 }[self.spec['type']]
357
358 def GetMachOType(self):
359 """Returns the MACH_O_TYPE of this target."""
360 # Weird, but matches Xcode.
361 if not self._IsBundle() and self.spec['type'] == 'executable':
362 return ''
363 return {
364 'executable': 'mh_execute',
365 'static_library': 'staticlib',
366 'shared_library': 'mh_dylib',
367 'loadable_module': 'mh_bundle',
368 }[self.spec['type']]
369
370 def _GetBundleBinaryPath(self):
371 """Returns the name of the bundle binary of by this target.
372 E.g. Chromium.app/Contents/MacOS/Chromium. Only valid for bundles."""
373 assert self._IsBundle()
374 if self.spec['type'] in ('shared_library') or self.isIOS:
375 path = self.GetBundleContentsFolderPath()
376 elif self.spec['type'] in ('executable', 'loadable_module'):
377 path = os.path.join(self.GetBundleContentsFolderPath(), 'MacOS')
378 return os.path.join(path, self.GetExecutableName())
379
380 def _GetStandaloneExecutableSuffix(self):
381 if 'product_extension' in self.spec:
382 return '.' + self.spec['product_extension']
383 return {
384 'executable': '',
385 'static_library': '.a',
386 'shared_library': '.dylib',
387 'loadable_module': '.so',
388 }[self.spec['type']]
389
390 def _GetStandaloneExecutablePrefix(self):
391 return self.spec.get('product_prefix', {
392 'executable': '',
393 'static_library': 'lib',
394 'shared_library': 'lib',
395 # Non-bundled loadable_modules are called foo.so for some reason
396 # (that is, .so and no prefix) with the xcode build -- match that.
397 'loadable_module': '',
398 }[self.spec['type']])
399
400 def _GetStandaloneBinaryPath(self):
401 """Returns the name of the non-bundle binary represented by this target.
402 E.g. hello_world. Only valid for non-bundles."""
403 assert not self._IsBundle()
404 assert self.spec['type'] in (
405 'executable', 'shared_library', 'static_library', 'loadable_module'), (
406 'Unexpected type %s' % self.spec['type'])
407 target = self.spec['target_name']
408 if self.spec['type'] == 'static_library':
409 if target[:3] == 'lib':
410 target = target[3:]
411 elif self.spec['type'] in ('loadable_module', 'shared_library'):
412 if target[:3] == 'lib':
413 target = target[3:]
414
415 target_prefix = self._GetStandaloneExecutablePrefix()
416 target = self.spec.get('product_name', target)
417 target_ext = self._GetStandaloneExecutableSuffix()
418 return target_prefix + target + target_ext
419
420 def GetExecutableName(self):
421 """Returns the executable name of the bundle represented by this target.
422 E.g. Chromium."""
423 if self._IsBundle():
424 return self.spec.get('product_name', self.spec['target_name'])
425 else:
426 return self._GetStandaloneBinaryPath()
427
428 def GetExecutablePath(self):
429 """Returns the directory name of the bundle represented by this target. E.g.
430 Chromium.app/Contents/MacOS/Chromium."""
431 if self._IsBundle():
432 return self._GetBundleBinaryPath()
433 else:
434 return self._GetStandaloneBinaryPath()
435
436 def GetActiveArchs(self, configname):
437 """Returns the architectures this target should be built for."""
438 config_settings = self.xcode_settings[configname]
439 xcode_archs_default = GetXcodeArchsDefault()
440 return xcode_archs_default.ActiveArchs(
441 config_settings.get('ARCHS'),
442 config_settings.get('VALID_ARCHS'),
443 config_settings.get('SDKROOT'))
444
445 def _GetSdkVersionInfoItem(self, sdk, infoitem):
446 # xcodebuild requires Xcode and can't run on Command Line Tools-only
447 # systems from 10.7 onward.
448 # Since the CLT has no SDK paths anyway, returning None is the
449 # most sensible route and should still do the right thing.
450 try:
451 return GetStdout(['xcrun', '--sdk', sdk, infoitem])
452 except:
453 pass
454
455 def _SdkRoot(self, configname):
456 if configname is None:
457 configname = self.configname
458 return self.GetPerConfigSetting('SDKROOT', configname, default='')
459
460 def _XcodePlatformPath(self, configname=None):
461 sdk_root = self._SdkRoot(configname)
462 if sdk_root not in XcodeSettings._platform_path_cache:
463 platform_path = self._GetSdkVersionInfoItem(sdk_root,
464 '--show-sdk-platform-path')
465 XcodeSettings._platform_path_cache[sdk_root] = platform_path
466 return XcodeSettings._platform_path_cache[sdk_root]
467
468 def _SdkPath(self, configname=None):
469 sdk_root = self._SdkRoot(configname)
470 if sdk_root.startswith('/'):
471 return sdk_root
472 return self._XcodeSdkPath(sdk_root)
473
474 def _XcodeSdkPath(self, sdk_root):
475 if sdk_root not in XcodeSettings._sdk_path_cache:
476 sdk_path = self._GetSdkVersionInfoItem(sdk_root, '--show-sdk-path')
477 XcodeSettings._sdk_path_cache[sdk_root] = sdk_path
478 if sdk_root:
479 XcodeSettings._sdk_root_cache[sdk_path] = sdk_root
480 return XcodeSettings._sdk_path_cache[sdk_root]
481
482 def _AppendPlatformVersionMinFlags(self, lst):
483 self._Appendf(lst, 'MACOSX_DEPLOYMENT_TARGET', '-mmacosx-version-min=%s')
484 if 'IPHONEOS_DEPLOYMENT_TARGET' in self._Settings():
485 # TODO: Implement this better?
486 sdk_path_basename = os.path.basename(self._SdkPath())
487 if sdk_path_basename.lower().startswith('iphonesimulator'):
488 self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET',
489 '-mios-simulator-version-min=%s')
490 else:
491 self._Appendf(lst, 'IPHONEOS_DEPLOYMENT_TARGET',
492 '-miphoneos-version-min=%s')
493
494 def GetCflags(self, configname, arch=None):
495 """Returns flags that need to be added to .c, .cc, .m, and .mm
496 compilations."""
497 # This functions (and the similar ones below) do not offer complete
498 # emulation of all xcode_settings keys. They're implemented on demand.
499
500 self.configname = configname
501 cflags = []
502
503 sdk_root = self._SdkPath()
504 if 'SDKROOT' in self._Settings() and sdk_root:
505 cflags.append('-isysroot %s' % sdk_root)
506
507 if self.header_map_path:
508 cflags.append('-I%s' % self.header_map_path)
509
510 if self._Test('CLANG_WARN_CONSTANT_CONVERSION', 'YES', default='NO'):
511 cflags.append('-Wconstant-conversion')
512
513 if self._Test('GCC_CHAR_IS_UNSIGNED_CHAR', 'YES', default='NO'):
514 cflags.append('-funsigned-char')
515
516 if self._Test('GCC_CW_ASM_SYNTAX', 'YES', default='YES'):
517 cflags.append('-fasm-blocks')
518
519 if 'GCC_DYNAMIC_NO_PIC' in self._Settings():
520 if self._Settings()['GCC_DYNAMIC_NO_PIC'] == 'YES':
521 cflags.append('-mdynamic-no-pic')
522 else:
523 pass
524 # TODO: In this case, it depends on the target. xcode passes
525 # mdynamic-no-pic by default for executable and possibly static lib
526 # according to mento
527
528 if self._Test('GCC_ENABLE_PASCAL_STRINGS', 'YES', default='YES'):
529 cflags.append('-mpascal-strings')
530
531 self._Appendf(cflags, 'GCC_OPTIMIZATION_LEVEL', '-O%s', default='s')
532
533 if self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES'):
534 dbg_format = self._Settings().get('DEBUG_INFORMATION_FORMAT', 'dwarf')
535 if dbg_format == 'dwarf':
536 cflags.append('-gdwarf-2')
537 elif dbg_format == 'stabs':
538 raise NotImplementedError('stabs debug format is not supported yet.')
539 elif dbg_format == 'dwarf-with-dsym':
540 cflags.append('-gdwarf-2')
541 else:
542 raise NotImplementedError('Unknown debug format %s' % dbg_format)
543
544 if self._Settings().get('GCC_STRICT_ALIASING') == 'YES':
545 cflags.append('-fstrict-aliasing')
546 elif self._Settings().get('GCC_STRICT_ALIASING') == 'NO':
547 cflags.append('-fno-strict-aliasing')
548
549 if self._Test('GCC_SYMBOLS_PRIVATE_EXTERN', 'YES', default='NO'):
550 cflags.append('-fvisibility=hidden')
551
552 if self._Test('GCC_TREAT_WARNINGS_AS_ERRORS', 'YES', default='NO'):
553 cflags.append('-Werror')
554
555 if self._Test('GCC_WARN_ABOUT_MISSING_NEWLINE', 'YES', default='NO'):
556 cflags.append('-Wnewline-eof')
557
558 # In Xcode, this is only activated when GCC_COMPILER_VERSION is clang or
559 # llvm-gcc. It also requires a fairly recent libtool, and
560 # if the system clang isn't used, DYLD_LIBRARY_PATH needs to contain the
561 # path to the libLTO.dylib that matches the used clang.
562 if self._Test('LLVM_LTO', 'YES', default='NO'):
563 cflags.append('-flto')
564
565 self._AppendPlatformVersionMinFlags(cflags)
566
567 # TODO:
568 if self._Test('COPY_PHASE_STRIP', 'YES', default='NO'):
569 self._WarnUnimplemented('COPY_PHASE_STRIP')
570 self._WarnUnimplemented('GCC_DEBUGGING_SYMBOLS')
571 self._WarnUnimplemented('GCC_ENABLE_OBJC_EXCEPTIONS')
572
573 # TODO: This is exported correctly, but assigning to it is not supported.
574 self._WarnUnimplemented('MACH_O_TYPE')
575 self._WarnUnimplemented('PRODUCT_TYPE')
576
577 if arch is not None:
578 archs = [arch]
579 else:
580 assert self.configname
581 archs = self.GetActiveArchs(self.configname)
582 if len(archs) != 1:
583 # TODO: Supporting fat binaries will be annoying.
584 self._WarnUnimplemented('ARCHS')
585 archs = ['i386']
586 cflags.append('-arch ' + archs[0])
587
588 if archs[0] in ('i386', 'x86_64'):
589 if self._Test('GCC_ENABLE_SSE3_EXTENSIONS', 'YES', default='NO'):
590 cflags.append('-msse3')
591 if self._Test('GCC_ENABLE_SUPPLEMENTAL_SSE3_INSTRUCTIONS', 'YES',
592 default='NO'):
593 cflags.append('-mssse3') # Note 3rd 's'.
594 if self._Test('GCC_ENABLE_SSE41_EXTENSIONS', 'YES', default='NO'):
595 cflags.append('-msse4.1')
596 if self._Test('GCC_ENABLE_SSE42_EXTENSIONS', 'YES', default='NO'):
597 cflags.append('-msse4.2')
598
599 cflags += self._Settings().get('WARNING_CFLAGS', [])
600
601 platform_root = self._XcodePlatformPath(configname)
602 if platform_root and self._IsXCTest():
603 cflags.append('-F' + platform_root + '/Developer/Library/Frameworks/')
604
605 if sdk_root:
606 framework_root = sdk_root
607 else:
608 framework_root = ''
609 config = self.spec['configurations'][self.configname]
610 framework_dirs = config.get('mac_framework_dirs', [])
611 for directory in framework_dirs:
612 cflags.append('-F' + directory.replace('$(SDKROOT)', framework_root))
613
614 self.configname = None
615 return cflags
616
617 def GetCflagsC(self, configname):
618 """Returns flags that need to be added to .c, and .m compilations."""
619 self.configname = configname
620 cflags_c = []
621 if self._Settings().get('GCC_C_LANGUAGE_STANDARD', '') == 'ansi':
622 cflags_c.append('-ansi')
623 else:
624 self._Appendf(cflags_c, 'GCC_C_LANGUAGE_STANDARD', '-std=%s')
625 cflags_c += self._Settings().get('OTHER_CFLAGS', [])
626 self.configname = None
627 return cflags_c
628
629 def GetCflagsCC(self, configname):
630 """Returns flags that need to be added to .cc, and .mm compilations."""
631 self.configname = configname
632 cflags_cc = []
633
634 clang_cxx_language_standard = self._Settings().get(
635 'CLANG_CXX_LANGUAGE_STANDARD')
636 # Note: Don't make c++0x to c++11 so that c++0x can be used with older
637 # clangs that don't understand c++11 yet (like Xcode 4.2's).
638 if clang_cxx_language_standard:
639 cflags_cc.append('-std=%s' % clang_cxx_language_standard)
640
641 self._Appendf(cflags_cc, 'CLANG_CXX_LIBRARY', '-stdlib=%s')
642
643 if self._Test('GCC_ENABLE_CPP_RTTI', 'NO', default='YES'):
644 cflags_cc.append('-fno-rtti')
645 if self._Test('GCC_ENABLE_CPP_EXCEPTIONS', 'NO', default='YES'):
646 cflags_cc.append('-fno-exceptions')
647 if self._Test('GCC_INLINES_ARE_PRIVATE_EXTERN', 'YES', default='NO'):
648 cflags_cc.append('-fvisibility-inlines-hidden')
649 if self._Test('GCC_THREADSAFE_STATICS', 'NO', default='YES'):
650 cflags_cc.append('-fno-threadsafe-statics')
651 # Note: This flag is a no-op for clang, it only has an effect for gcc.
652 if self._Test('GCC_WARN_ABOUT_INVALID_OFFSETOF_MACRO', 'NO', default='YES'):
653 cflags_cc.append('-Wno-invalid-offsetof')
654
655 other_ccflags = []
656
657 for flag in self._Settings().get('OTHER_CPLUSPLUSFLAGS', ['$(inherited)']):
658 # TODO: More general variable expansion. Missing in many other places too.
659 if flag in ('$inherited', '$(inherited)', '${inherited}'):
660 flag = '$OTHER_CFLAGS'
661 if flag in ('$OTHER_CFLAGS', '$(OTHER_CFLAGS)', '${OTHER_CFLAGS}'):
662 other_ccflags += self._Settings().get('OTHER_CFLAGS', [])
663 else:
664 other_ccflags.append(flag)
665 cflags_cc += other_ccflags
666
667 self.configname = None
668 return cflags_cc
669
670 def _AddObjectiveCGarbageCollectionFlags(self, flags):
671 gc_policy = self._Settings().get('GCC_ENABLE_OBJC_GC', 'unsupported')
672 if gc_policy == 'supported':
673 flags.append('-fobjc-gc')
674 elif gc_policy == 'required':
675 flags.append('-fobjc-gc-only')
676
677 def _AddObjectiveCARCFlags(self, flags):
678 if self._Test('CLANG_ENABLE_OBJC_ARC', 'YES', default='NO'):
679 flags.append('-fobjc-arc')
680
681 def _AddObjectiveCMissingPropertySynthesisFlags(self, flags):
682 if self._Test('CLANG_WARN_OBJC_MISSING_PROPERTY_SYNTHESIS',
683 'YES', default='NO'):
684 flags.append('-Wobjc-missing-property-synthesis')
685
686 def GetCflagsObjC(self, configname):
687 """Returns flags that need to be added to .m compilations."""
688 self.configname = configname
689 cflags_objc = []
690 self._AddObjectiveCGarbageCollectionFlags(cflags_objc)
691 self._AddObjectiveCARCFlags(cflags_objc)
692 self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objc)
693 self.configname = None
694 return cflags_objc
695
696 def GetCflagsObjCC(self, configname):
697 """Returns flags that need to be added to .mm compilations."""
698 self.configname = configname
699 cflags_objcc = []
700 self._AddObjectiveCGarbageCollectionFlags(cflags_objcc)
701 self._AddObjectiveCARCFlags(cflags_objcc)
702 self._AddObjectiveCMissingPropertySynthesisFlags(cflags_objcc)
703 if self._Test('GCC_OBJC_CALL_CXX_CDTORS', 'YES', default='NO'):
704 cflags_objcc.append('-fobjc-call-cxx-cdtors')
705 self.configname = None
706 return cflags_objcc
707
708 def GetInstallNameBase(self):
709 """Return DYLIB_INSTALL_NAME_BASE for this target."""
710 # Xcode sets this for shared_libraries, and for nonbundled loadable_modules.
711 if (self.spec['type'] != 'shared_library' and
712 (self.spec['type'] != 'loadable_module' or self._IsBundle())):
713 return None
714 install_base = self.GetPerTargetSetting(
715 'DYLIB_INSTALL_NAME_BASE',
716 default='/Library/Frameworks' if self._IsBundle() else '/usr/local/lib')
717 return install_base
718
719 def _StandardizePath(self, path):
720 """Do :standardizepath processing for path."""
721 # I'm not quite sure what :standardizepath does. Just call normpath(),
722 # but don't let @executable_path/../foo collapse to foo.
723 if '/' in path:
724 prefix, rest = '', path
725 if path.startswith('@'):
726 prefix, rest = path.split('/', 1)
727 rest = os.path.normpath(rest) # :standardizepath
728 path = os.path.join(prefix, rest)
729 return path
730
731 def GetInstallName(self):
732 """Return LD_DYLIB_INSTALL_NAME for this target."""
733 # Xcode sets this for shared_libraries, and for nonbundled loadable_modules.
734 if (self.spec['type'] != 'shared_library' and
735 (self.spec['type'] != 'loadable_module' or self._IsBundle())):
736 return None
737
738 default_install_name = \
739 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)/$(EXECUTABLE_PATH)'
740 install_name = self.GetPerTargetSetting(
741 'LD_DYLIB_INSTALL_NAME', default=default_install_name)
742
743 # Hardcode support for the variables used in chromium for now, to
744 # unblock people using the make build.
745 if '$' in install_name:
746 assert install_name in ('$(DYLIB_INSTALL_NAME_BASE:standardizepath)/'
747 '$(WRAPPER_NAME)/$(PRODUCT_NAME)', default_install_name), (
748 'Variables in LD_DYLIB_INSTALL_NAME are not generally supported '
749 'yet in target \'%s\' (got \'%s\')' %
750 (self.spec['target_name'], install_name))
751
752 install_name = install_name.replace(
753 '$(DYLIB_INSTALL_NAME_BASE:standardizepath)',
754 self._StandardizePath(self.GetInstallNameBase()))
755 if self._IsBundle():
756 # These are only valid for bundles, hence the |if|.
757 install_name = install_name.replace(
758 '$(WRAPPER_NAME)', self.GetWrapperName())
759 install_name = install_name.replace(
760 '$(PRODUCT_NAME)', self.GetProductName())
761 else:
762 assert '$(WRAPPER_NAME)' not in install_name
763 assert '$(PRODUCT_NAME)' not in install_name
764
765 install_name = install_name.replace(
766 '$(EXECUTABLE_PATH)', self.GetExecutablePath())
767 return install_name
768
769 def _MapLinkerFlagFilename(self, ldflag, gyp_to_build_path):
770 """Checks if ldflag contains a filename and if so remaps it from
771 gyp-directory-relative to build-directory-relative."""
772 # This list is expanded on demand.
773 # They get matched as:
774 # -exported_symbols_list file
775 # -Wl,exported_symbols_list file
776 # -Wl,exported_symbols_list,file
777 LINKER_FILE = r'(\S+)'
778 WORD = r'\S+'
779 linker_flags = [
780 ['-exported_symbols_list', LINKER_FILE], # Needed for NaCl.
781 ['-unexported_symbols_list', LINKER_FILE],
782 ['-reexported_symbols_list', LINKER_FILE],
783 ['-sectcreate', WORD, WORD, LINKER_FILE], # Needed for remoting.
784 ]
785 for flag_pattern in linker_flags:
786 regex = re.compile('(?:-Wl,)?' + '[ ,]'.join(flag_pattern))
787 m = regex.match(ldflag)
788 if m:
789 ldflag = ldflag[:m.start(1)] + gyp_to_build_path(m.group(1)) + \
790 ldflag[m.end(1):]
791 # Required for ffmpeg (no idea why they don't use LIBRARY_SEARCH_PATHS,
792 # TODO(thakis): Update ffmpeg.gyp):
793 if ldflag.startswith('-L'):
794 ldflag = '-L' + gyp_to_build_path(ldflag[len('-L'):])
795 return ldflag
796
797 def GetLdflags(self, configname, product_dir, gyp_to_build_path, arch=None):
798 """Returns flags that need to be passed to the linker.
799
800 Args:
801 configname: The name of the configuration to get ld flags for.
802 product_dir: The directory where products such static and dynamic
803 libraries are placed. This is added to the library search path.
804 gyp_to_build_path: A function that converts paths relative to the
805 current gyp file to paths relative to the build direcotry.
806 """
807 self.configname = configname
808 ldflags = []
809
810 # The xcode build is relative to a gyp file's directory, and OTHER_LDFLAGS
811 # can contain entries that depend on this. Explicitly absolutify these.
812 for ldflag in self._Settings().get('OTHER_LDFLAGS', []):
813 ldflags.append(self._MapLinkerFlagFilename(ldflag, gyp_to_build_path))
814
815 if self._Test('DEAD_CODE_STRIPPING', 'YES', default='NO'):
816 ldflags.append('-Wl,-dead_strip')
817
818 if self._Test('PREBINDING', 'YES', default='NO'):
819 ldflags.append('-Wl,-prebind')
820
821 self._Appendf(
822 ldflags, 'DYLIB_COMPATIBILITY_VERSION', '-compatibility_version %s')
823 self._Appendf(
824 ldflags, 'DYLIB_CURRENT_VERSION', '-current_version %s')
825
826 self._AppendPlatformVersionMinFlags(ldflags)
827
828 if 'SDKROOT' in self._Settings() and self._SdkPath():
829 ldflags.append('-isysroot ' + self._SdkPath())
830
831 for library_path in self._Settings().get('LIBRARY_SEARCH_PATHS', []):
832 ldflags.append('-L' + gyp_to_build_path(library_path))
833
834 if 'ORDER_FILE' in self._Settings():
835 ldflags.append('-Wl,-order_file ' +
836 '-Wl,' + gyp_to_build_path(
837 self._Settings()['ORDER_FILE']))
838
839 if arch is not None:
840 archs = [arch]
841 else:
842 assert self.configname
843 archs = self.GetActiveArchs(self.configname)
844 if len(archs) != 1:
845 # TODO: Supporting fat binaries will be annoying.
846 self._WarnUnimplemented('ARCHS')
847 archs = ['i386']
848 ldflags.append('-arch ' + archs[0])
849
850 # Xcode adds the product directory by default.
851 # Rewrite -L. to -L./ to work around http://www.openradar.me/25313838
852 ldflags.append('-L' + (product_dir if product_dir != '.' else './'))
853
854 install_name = self.GetInstallName()
855 if install_name and self.spec['type'] != 'loadable_module':
856 ldflags.append('-install_name ' + install_name.replace(' ', r'\ '))
857
858 for rpath in self._Settings().get('LD_RUNPATH_SEARCH_PATHS', []):
859 ldflags.append('-Wl,-rpath,' + rpath)
860
861 sdk_root = self._SdkPath()
862 if not sdk_root:
863 sdk_root = ''
864 config = self.spec['configurations'][self.configname]
865 framework_dirs = config.get('mac_framework_dirs', [])
866 for directory in framework_dirs:
867 ldflags.append('-F' + directory.replace('$(SDKROOT)', sdk_root))
868
869 platform_root = self._XcodePlatformPath(configname)
870 if sdk_root and platform_root and self._IsXCTest():
871 ldflags.append('-F' + platform_root + '/Developer/Library/Frameworks/')
872 ldflags.append('-framework XCTest')
873
874 is_extension = self._IsIosAppExtension() or self._IsIosWatchKitExtension()
875 if sdk_root and is_extension:
876 # Adds the link flags for extensions. These flags are common for all
877 # extensions and provide loader and main function.
878 # These flags reflect the compilation options used by xcode to compile
879 # extensions.
880 if XcodeVersion() < '0900':
881 ldflags.append('-lpkstart')
882 ldflags.append(sdk_root +
883 '/System/Library/PrivateFrameworks/PlugInKit.framework/PlugInKit')
884 else:
885 ldflags.append('-e _NSExtensionMain')
886 ldflags.append('-fapplication-extension')
887
888 self._Appendf(ldflags, 'CLANG_CXX_LIBRARY', '-stdlib=%s')
889
890 self.configname = None
891 return ldflags
892
893 def GetLibtoolflags(self, configname):
894 """Returns flags that need to be passed to the static linker.
895
896 Args:
897 configname: The name of the configuration to get ld flags for.
898 """
899 self.configname = configname
900 libtoolflags = []
901
902 for libtoolflag in self._Settings().get('OTHER_LDFLAGS', []):
903 libtoolflags.append(libtoolflag)
904 # TODO(thakis): ARCHS?
905
906 self.configname = None
907 return libtoolflags
908
909 def GetPerTargetSettings(self):
910 """Gets a list of all the per-target settings. This will only fetch keys
911 whose values are the same across all configurations."""
912 first_pass = True
913 result = {}
914 for configname in sorted(self.xcode_settings.keys()):
915 if first_pass:
916 result = dict(self.xcode_settings[configname])
917 first_pass = False
918 else:
919 for key, value in self.xcode_settings[configname].iteritems():
920 if key not in result:
921 continue
922 elif result[key] != value:
923 del result[key]
924 return result
925
926 def GetPerConfigSetting(self, setting, configname, default=None):
927 if configname in self.xcode_settings:
928 return self.xcode_settings[configname].get(setting, default)
929 else:
930 return self.GetPerTargetSetting(setting, default)
931
932 def GetPerTargetSetting(self, setting, default=None):
933 """Tries to get xcode_settings.setting from spec. Assumes that the setting
934 has the same value in all configurations and throws otherwise."""
935 is_first_pass = True
936 result = None
937 for configname in sorted(self.xcode_settings.keys()):
938 if is_first_pass:
939 result = self.xcode_settings[configname].get(setting, None)
940 is_first_pass = False
941 else:
942 assert result == self.xcode_settings[configname].get(setting, None), (
943 "Expected per-target setting for '%s', got per-config setting "
944 "(target %s)" % (setting, self.spec['target_name']))
945 if result is None:
946 return default
947 return result
948
949 def _GetStripPostbuilds(self, configname, output_binary, quiet):
950 """Returns a list of shell commands that contain the shell commands
951 neccessary to strip this target's binary. These should be run as postbuilds
952 before the actual postbuilds run."""
953 self.configname = configname
954
955 result = []
956 if (self._Test('DEPLOYMENT_POSTPROCESSING', 'YES', default='NO') and
957 self._Test('STRIP_INSTALLED_PRODUCT', 'YES', default='NO')):
958
959 default_strip_style = 'debugging'
960 if ((self.spec['type'] == 'loadable_module' or self._IsIosAppExtension())
961 and self._IsBundle()):
962 default_strip_style = 'non-global'
963 elif self.spec['type'] == 'executable':
964 default_strip_style = 'all'
965
966 strip_style = self._Settings().get('STRIP_STYLE', default_strip_style)
967 strip_flags = {
968 'all': '',
969 'non-global': '-x',
970 'debugging': '-S',
971 }[strip_style]
972
973 explicit_strip_flags = self._Settings().get('STRIPFLAGS', '')
974 if explicit_strip_flags:
975 strip_flags += ' ' + _NormalizeEnvVarReferences(explicit_strip_flags)
976
977 if not quiet:
978 result.append('echo STRIP\\(%s\\)' % self.spec['target_name'])
979 result.append('strip %s %s' % (strip_flags, output_binary))
980
981 self.configname = None
982 return result
983
984 def _GetDebugInfoPostbuilds(self, configname, output, output_binary, quiet):
985 """Returns a list of shell commands that contain the shell commands
986 neccessary to massage this target's debug information. These should be run
987 as postbuilds before the actual postbuilds run."""
988 self.configname = configname
989
990 # For static libraries, no dSYMs are created.
991 result = []
992 if (self._Test('GCC_GENERATE_DEBUGGING_SYMBOLS', 'YES', default='YES') and
993 self._Test(
994 'DEBUG_INFORMATION_FORMAT', 'dwarf-with-dsym', default='dwarf') and
995 self.spec['type'] != 'static_library'):
996 if not quiet:
997 result.append('echo DSYMUTIL\\(%s\\)' % self.spec['target_name'])
998 result.append('dsymutil %s -o %s' % (output_binary, output + '.dSYM'))
999
1000 self.configname = None
1001 return result
1002
1003 def _GetTargetPostbuilds(self, configname, output, output_binary,
1004 quiet=False):
1005 """Returns a list of shell commands that contain the shell commands
1006 to run as postbuilds for this target, before the actual postbuilds."""
1007 # dSYMs need to build before stripping happens.
1008 return (
1009 self._GetDebugInfoPostbuilds(configname, output, output_binary, quiet) +
1010 self._GetStripPostbuilds(configname, output_binary, quiet))
1011
1012 def _GetIOSPostbuilds(self, configname, output_binary):
1013 """Return a shell command to codesign the iOS output binary so it can
1014 be deployed to a device. This should be run as the very last step of the
1015 build."""
1016 if not (self.isIOS and
1017 (self.spec['type'] == 'executable' or self._IsXCTest()) or
1018 self.IsIosFramework()):
1019 return []
1020
1021 postbuilds = []
1022 product_name = self.GetFullProductName()
1023 settings = self.xcode_settings[configname]
1024
1025 # Xcode expects XCTests to be copied into the TEST_HOST dir.
1026 if self._IsXCTest():
1027 source = os.path.join("${BUILT_PRODUCTS_DIR}", product_name)
1028 test_host = os.path.dirname(settings.get('TEST_HOST'));
1029 xctest_destination = os.path.join(test_host, 'PlugIns', product_name)
1030 postbuilds.extend(['ditto %s %s' % (source, xctest_destination)])
1031
1032 key = self._GetIOSCodeSignIdentityKey(settings)
1033 if not key:
1034 return postbuilds
1035
1036 # Warn for any unimplemented signing xcode keys.
1037 unimpl = ['OTHER_CODE_SIGN_FLAGS']
1038 unimpl = set(unimpl) & set(self.xcode_settings[configname].keys())
1039 if unimpl:
1040 print 'Warning: Some codesign keys not implemented, ignoring: %s' % (
1041 ', '.join(sorted(unimpl)))
1042
1043 if self._IsXCTest():
1044 # For device xctests, Xcode copies two extra frameworks into $TEST_HOST.
1045 test_host = os.path.dirname(settings.get('TEST_HOST'));
1046 frameworks_dir = os.path.join(test_host, 'Frameworks')
1047 platform_root = self._XcodePlatformPath(configname)
1048 frameworks = \
1049 ['Developer/Library/PrivateFrameworks/IDEBundleInjection.framework',
1050 'Developer/Library/Frameworks/XCTest.framework']
1051 for framework in frameworks:
1052 source = os.path.join(platform_root, framework)
1053 destination = os.path.join(frameworks_dir, os.path.basename(framework))
1054 postbuilds.extend(['ditto %s %s' % (source, destination)])
1055
1056 # Then re-sign everything with 'preserve=True'
1057 postbuilds.extend(['%s code-sign-bundle "%s" "%s" "%s" "%s" %s' % (
1058 os.path.join('${TARGET_BUILD_DIR}', 'gyp-mac-tool'), key,
1059 settings.get('CODE_SIGN_ENTITLEMENTS', ''),
1060 settings.get('PROVISIONING_PROFILE', ''), destination, True)
1061 ])
1062 plugin_dir = os.path.join(test_host, 'PlugIns')
1063 targets = [os.path.join(plugin_dir, product_name), test_host]
1064 for target in targets:
1065 postbuilds.extend(['%s code-sign-bundle "%s" "%s" "%s" "%s" %s' % (
1066 os.path.join('${TARGET_BUILD_DIR}', 'gyp-mac-tool'), key,
1067 settings.get('CODE_SIGN_ENTITLEMENTS', ''),
1068 settings.get('PROVISIONING_PROFILE', ''), target, True)
1069 ])
1070
1071 postbuilds.extend(['%s code-sign-bundle "%s" "%s" "%s" "%s" %s' % (
1072 os.path.join('${TARGET_BUILD_DIR}', 'gyp-mac-tool'), key,
1073 settings.get('CODE_SIGN_ENTITLEMENTS', ''),
1074 settings.get('PROVISIONING_PROFILE', ''),
1075 os.path.join("${BUILT_PRODUCTS_DIR}", product_name), False)
1076 ])
1077 return postbuilds
1078
1079 def _GetIOSCodeSignIdentityKey(self, settings):
1080 identity = settings.get('CODE_SIGN_IDENTITY')
1081 if not identity:
1082 return None
1083 if identity not in XcodeSettings._codesigning_key_cache:
1084 output = subprocess.check_output(
1085 ['security', 'find-identity', '-p', 'codesigning', '-v'])
1086 for line in output.splitlines():
1087 if identity in line:
1088 fingerprint = line.split()[1]
1089 cache = XcodeSettings._codesigning_key_cache
1090 assert identity not in cache or fingerprint == cache[identity], (
1091 "Multiple codesigning fingerprints for identity: %s" % identity)
1092 XcodeSettings._codesigning_key_cache[identity] = fingerprint
1093 return XcodeSettings._codesigning_key_cache.get(identity, '')
1094
1095 def AddImplicitPostbuilds(self, configname, output, output_binary,
1096 postbuilds=[], quiet=False):
1097 """Returns a list of shell commands that should run before and after
1098 |postbuilds|."""
1099 assert output_binary is not None
1100 pre = self._GetTargetPostbuilds(configname, output, output_binary, quiet)
1101 post = self._GetIOSPostbuilds(configname, output_binary)
1102 return pre + postbuilds + post
1103
1104 def _AdjustLibrary(self, library, config_name=None):
1105 if library.endswith('.framework'):
1106 l = '-framework ' + os.path.splitext(os.path.basename(library))[0]
1107 else:
1108 m = self.library_re.match(library)
1109 if m:
1110 l = '-l' + m.group(1)
1111 else:
1112 l = library
1113
1114 sdk_root = self._SdkPath(config_name)
1115 if not sdk_root:
1116 sdk_root = ''
1117 # Xcode 7 started shipping with ".tbd" (text based stubs) files instead of
1118 # ".dylib" without providing a real support for them. What it does, for
1119 # "/usr/lib" libraries, is do "-L/usr/lib -lname" which is dependent on the
1120 # library order and cause collision when building Chrome.
1121 #
1122 # Instead substitude ".tbd" to ".dylib" in the generated project when the
1123 # following conditions are both true:
1124 # - library is referenced in the gyp file as "$(SDKROOT)/**/*.dylib",
1125 # - the ".dylib" file does not exists but a ".tbd" file do.
1126 library = l.replace('$(SDKROOT)', sdk_root)
1127 if l.startswith('$(SDKROOT)'):
1128 basename, ext = os.path.splitext(library)
1129 if ext == '.dylib' and not os.path.exists(library):
1130 tbd_library = basename + '.tbd'
1131 if os.path.exists(tbd_library):
1132 library = tbd_library
1133 return library
1134
1135 def AdjustLibraries(self, libraries, config_name=None):
1136 """Transforms entries like 'Cocoa.framework' in libraries into entries like
1137 '-framework Cocoa', 'libcrypto.dylib' into '-lcrypto', etc.
1138 """
1139 libraries = [self._AdjustLibrary(library, config_name)
1140 for library in libraries]
1141 return libraries
1142
1143 def _BuildMachineOSBuild(self):
1144 return GetStdout(['sw_vers', '-buildVersion'])
1145
1146 def _XcodeIOSDeviceFamily(self, configname):
1147 family = self.xcode_settings[configname].get('TARGETED_DEVICE_FAMILY', '1')
1148 return [int(x) for x in family.split(',')]
1149
1150 def GetExtraPlistItems(self, configname=None):
1151 """Returns a dictionary with extra items to insert into Info.plist."""
1152 if configname not in XcodeSettings._plist_cache:
1153 cache = {}
1154 cache['BuildMachineOSBuild'] = self._BuildMachineOSBuild()
1155
1156 xcode, xcode_build = XcodeVersion()
1157 cache['DTXcode'] = xcode
1158 cache['DTXcodeBuild'] = xcode_build
1159 compiler = self.xcode_settings[configname].get('GCC_VERSION')
1160 if compiler is not None:
1161 cache['DTCompiler'] = compiler
1162
1163 sdk_root = self._SdkRoot(configname)
1164 if not sdk_root:
1165 sdk_root = self._DefaultSdkRoot()
1166 sdk_version = self._GetSdkVersionInfoItem(sdk_root, '--show-sdk-version')
1167 cache['DTSDKName'] = sdk_root + (sdk_version or '')
1168 if xcode >= '0720':
1169 cache['DTSDKBuild'] = self._GetSdkVersionInfoItem(
1170 sdk_root, '--show-sdk-build-version')
1171 elif xcode >= '0430':
1172 cache['DTSDKBuild'] = sdk_version
1173 else:
1174 cache['DTSDKBuild'] = cache['BuildMachineOSBuild']
1175
1176 if self.isIOS:
1177 cache['MinimumOSVersion'] = self.xcode_settings[configname].get(
1178 'IPHONEOS_DEPLOYMENT_TARGET')
1179 cache['DTPlatformName'] = sdk_root
1180 cache['DTPlatformVersion'] = sdk_version
1181
1182 if configname.endswith("iphoneos"):
1183 cache['CFBundleSupportedPlatforms'] = ['iPhoneOS']
1184 cache['DTPlatformBuild'] = cache['DTSDKBuild']
1185 else:
1186 cache['CFBundleSupportedPlatforms'] = ['iPhoneSimulator']
1187 # This is weird, but Xcode sets DTPlatformBuild to an empty field
1188 # for simulator builds.
1189 cache['DTPlatformBuild'] = ""
1190 XcodeSettings._plist_cache[configname] = cache
1191
1192 # Include extra plist items that are per-target, not per global
1193 # XcodeSettings.
1194 items = dict(XcodeSettings._plist_cache[configname])
1195 if self.isIOS:
1196 items['UIDeviceFamily'] = self._XcodeIOSDeviceFamily(configname)
1197 return items
1198
1199 def _DefaultSdkRoot(self):
1200 """Returns the default SDKROOT to use.
1201
1202 Prior to version 5.0.0, if SDKROOT was not explicitly set in the Xcode
1203 project, then the environment variable was empty. Starting with this
1204 version, Xcode uses the name of the newest SDK installed.
1205 """
1206 xcode_version, xcode_build = XcodeVersion()
1207 if xcode_version < '0500':
1208 return ''
1209 default_sdk_path = self._XcodeSdkPath('')
1210 default_sdk_root = XcodeSettings._sdk_root_cache.get(default_sdk_path)
1211 if default_sdk_root:
1212 return default_sdk_root
1213 try:
1214 all_sdks = GetStdout(['xcodebuild', '-showsdks'])
1215 except:
1216 # If xcodebuild fails, there will be no valid SDKs
1217 return ''
1218 for line in all_sdks.splitlines():
1219 items = line.split()
1220 if len(items) >= 3 and items[-2] == '-sdk':
1221 sdk_root = items[-1]
1222 sdk_path = self._XcodeSdkPath(sdk_root)
1223 if sdk_path == default_sdk_path:
1224 return sdk_root
1225 return ''
1226
1227
1228class MacPrefixHeader(object):
1229 """A class that helps with emulating Xcode's GCC_PREFIX_HEADER feature.
1230
1231 This feature consists of several pieces:
1232 * If GCC_PREFIX_HEADER is present, all compilations in that project get an
1233 additional |-include path_to_prefix_header| cflag.
1234 * If GCC_PRECOMPILE_PREFIX_HEADER is present too, then the prefix header is
1235 instead compiled, and all other compilations in the project get an
1236 additional |-include path_to_compiled_header| instead.
1237 + Compiled prefix headers have the extension gch. There is one gch file for
1238 every language used in the project (c, cc, m, mm), since gch files for
1239 different languages aren't compatible.
1240 + gch files themselves are built with the target's normal cflags, but they
1241 obviously don't get the |-include| flag. Instead, they need a -x flag that
1242 describes their language.
1243 + All o files in the target need to depend on the gch file, to make sure
1244 it's built before any o file is built.
1245
1246 This class helps with some of these tasks, but it needs help from the build
1247 system for writing dependencies to the gch files, for writing build commands
1248 for the gch files, and for figuring out the location of the gch files.
1249 """
1250 def __init__(self, xcode_settings,
1251 gyp_path_to_build_path, gyp_path_to_build_output):
1252 """If xcode_settings is None, all methods on this class are no-ops.
1253
1254 Args:
1255 gyp_path_to_build_path: A function that takes a gyp-relative path,
1256 and returns a path relative to the build directory.
1257 gyp_path_to_build_output: A function that takes a gyp-relative path and
1258 a language code ('c', 'cc', 'm', or 'mm'), and that returns a path
1259 to where the output of precompiling that path for that language
1260 should be placed (without the trailing '.gch').
1261 """
1262 # This doesn't support per-configuration prefix headers. Good enough
1263 # for now.
1264 self.header = None
1265 self.compile_headers = False
1266 if xcode_settings:
1267 self.header = xcode_settings.GetPerTargetSetting('GCC_PREFIX_HEADER')
1268 self.compile_headers = xcode_settings.GetPerTargetSetting(
1269 'GCC_PRECOMPILE_PREFIX_HEADER', default='NO') != 'NO'
1270 self.compiled_headers = {}
1271 if self.header:
1272 if self.compile_headers:
1273 for lang in ['c', 'cc', 'm', 'mm']:
1274 self.compiled_headers[lang] = gyp_path_to_build_output(
1275 self.header, lang)
1276 self.header = gyp_path_to_build_path(self.header)
1277
1278 def _CompiledHeader(self, lang, arch):
1279 assert self.compile_headers
1280 h = self.compiled_headers[lang]
1281 if arch:
1282 h += '.' + arch
1283 return h
1284
1285 def GetInclude(self, lang, arch=None):
1286 """Gets the cflags to include the prefix header for language |lang|."""
1287 if self.compile_headers and lang in self.compiled_headers:
1288 return '-include %s' % self._CompiledHeader(lang, arch)
1289 elif self.header:
1290 return '-include %s' % self.header
1291 else:
1292 return ''
1293
1294 def _Gch(self, lang, arch):
1295 """Returns the actual file name of the prefix header for language |lang|."""
1296 assert self.compile_headers
1297 return self._CompiledHeader(lang, arch) + '.gch'
1298
1299 def GetObjDependencies(self, sources, objs, arch=None):
1300 """Given a list of source files and the corresponding object files, returns
1301 a list of (source, object, gch) tuples, where |gch| is the build-directory
1302 relative path to the gch file each object file depends on. |compilable[i]|
1303 has to be the source file belonging to |objs[i]|."""
1304 if not self.header or not self.compile_headers:
1305 return []
1306
1307 result = []
1308 for source, obj in zip(sources, objs):
1309 ext = os.path.splitext(source)[1]
1310 lang = {
1311 '.c': 'c',
1312 '.cpp': 'cc', '.cc': 'cc', '.cxx': 'cc',
1313 '.m': 'm',
1314 '.mm': 'mm',
1315 }.get(ext, None)
1316 if lang:
1317 result.append((source, obj, self._Gch(lang, arch)))
1318 return result
1319
1320 def GetPchBuildCommands(self, arch=None):
1321 """Returns [(path_to_gch, language_flag, language, header)].
1322 |path_to_gch| and |header| are relative to the build directory.
1323 """
1324 if not self.header or not self.compile_headers:
1325 return []
1326 return [
1327 (self._Gch('c', arch), '-x c-header', 'c', self.header),
1328 (self._Gch('cc', arch), '-x c++-header', 'cc', self.header),
1329 (self._Gch('m', arch), '-x objective-c-header', 'm', self.header),
1330 (self._Gch('mm', arch), '-x objective-c++-header', 'mm', self.header),
1331 ]
1332
1333
1334def XcodeVersion():
1335 """Returns a tuple of version and build version of installed Xcode."""
1336 # `xcodebuild -version` output looks like
1337 # Xcode 4.6.3
1338 # Build version 4H1503
1339 # or like
1340 # Xcode 3.2.6
1341 # Component versions: DevToolsCore-1809.0; DevToolsSupport-1806.0
1342 # BuildVersion: 10M2518
1343 # Convert that to '0463', '4H1503'.
1344 global XCODE_VERSION_CACHE
1345 if XCODE_VERSION_CACHE:
1346 return XCODE_VERSION_CACHE
1347 try:
1348 version_list = GetStdout(['xcodebuild', '-version']).splitlines()
1349 # In some circumstances xcodebuild exits 0 but doesn't return
1350 # the right results; for example, a user on 10.7 or 10.8 with
1351 # a bogus path set via xcode-select
1352 # In that case this may be a CLT-only install so fall back to
1353 # checking that version.
1354 if len(version_list) < 2:
1355 raise GypError("xcodebuild returned unexpected results")
1356 except:
1357 version = CLTVersion()
1358 if version:
1359 version = re.match(r'(\d\.\d\.?\d*)', version).groups()[0]
1360 else:
1361 raise GypError("No Xcode or CLT version detected!")
1362 # The CLT has no build information, so we return an empty string.
1363 version_list = [version, '']
1364 version = version_list[0]
1365 build = version_list[-1]
1366 # Be careful to convert "4.2" to "0420":
1367 version = version.split()[-1].replace('.', '')
1368 version = (version + '0' * (3 - len(version))).zfill(4)
1369 if build:
1370 build = build.split()[-1]
1371 XCODE_VERSION_CACHE = (version, build)
1372 return XCODE_VERSION_CACHE
1373
1374
1375# This function ported from the logic in Homebrew's CLT version check
1376def CLTVersion():
1377 """Returns the version of command-line tools from pkgutil."""
1378 # pkgutil output looks like
1379 # package-id: com.apple.pkg.CLTools_Executables
1380 # version: 5.0.1.0.1.1382131676
1381 # volume: /
1382 # location: /
1383 # install-time: 1382544035
1384 # groups: com.apple.FindSystemFiles.pkg-group com.apple.DevToolsBoth.pkg-group com.apple.DevToolsNonRelocatableShared.pkg-group
1385 STANDALONE_PKG_ID = "com.apple.pkg.DeveloperToolsCLILeo"
1386 FROM_XCODE_PKG_ID = "com.apple.pkg.DeveloperToolsCLI"
1387 MAVERICKS_PKG_ID = "com.apple.pkg.CLTools_Executables"
1388
1389 regex = re.compile('version: (?P<version>.+)')
1390 for key in [MAVERICKS_PKG_ID, STANDALONE_PKG_ID, FROM_XCODE_PKG_ID]:
1391 try:
1392 output = GetStdout(['/usr/sbin/pkgutil', '--pkg-info', key])
1393 return re.search(regex, output).groupdict()['version']
1394 except:
1395 continue
1396
1397
1398def GetStdout(cmdlist):
1399 """Returns the content of standard output returned by invoking |cmdlist|.
1400 Raises |GypError| if the command return with a non-zero return code."""
1401 job = subprocess.Popen(cmdlist, stdout=subprocess.PIPE)
1402 out = job.communicate()[0]
1403 if job.returncode != 0:
1404 sys.stderr.write(out + '\n')
1405 raise GypError('Error %d running %s' % (job.returncode, cmdlist[0]))
1406 return out.rstrip('\n')
1407
1408
1409def MergeGlobalXcodeSettingsToSpec(global_dict, spec):
1410 """Merges the global xcode_settings dictionary into each configuration of the
1411 target represented by spec. For keys that are both in the global and the local
1412 xcode_settings dict, the local key gets precendence.
1413 """
1414 # The xcode generator special-cases global xcode_settings and does something
1415 # that amounts to merging in the global xcode_settings into each local
1416 # xcode_settings dict.
1417 global_xcode_settings = global_dict.get('xcode_settings', {})
1418 for config in spec['configurations'].values():
1419 if 'xcode_settings' in config:
1420 new_settings = global_xcode_settings.copy()
1421 new_settings.update(config['xcode_settings'])
1422 config['xcode_settings'] = new_settings
1423
1424
1425def IsMacBundle(flavor, spec):
1426 """Returns if |spec| should be treated as a bundle.
1427
1428 Bundles are directories with a certain subdirectory structure, instead of
1429 just a single file. Bundle rules do not produce a binary but also package
1430 resources into that directory."""
1431 is_mac_bundle = int(spec.get('mac_xctest_bundle', 0)) != 0 or \
1432 int(spec.get('mac_xcuitest_bundle', 0)) != 0 or \
1433 (int(spec.get('mac_bundle', 0)) != 0 and flavor == 'mac')
1434
1435 if is_mac_bundle:
1436 assert spec['type'] != 'none', (
1437 'mac_bundle targets cannot have type none (target "%s")' %
1438 spec['target_name'])
1439 return is_mac_bundle
1440
1441
1442def GetMacBundleResources(product_dir, xcode_settings, resources):
1443 """Yields (output, resource) pairs for every resource in |resources|.
1444 Only call this for mac bundle targets.
1445
1446 Args:
1447 product_dir: Path to the directory containing the output bundle,
1448 relative to the build directory.
1449 xcode_settings: The XcodeSettings of the current target.
1450 resources: A list of bundle resources, relative to the build directory.
1451 """
1452 dest = os.path.join(product_dir,
1453 xcode_settings.GetBundleResourceFolder())
1454 for res in resources:
1455 output = dest
1456
1457 # The make generator doesn't support it, so forbid it everywhere
1458 # to keep the generators more interchangable.
1459 assert ' ' not in res, (
1460 "Spaces in resource filenames not supported (%s)" % res)
1461
1462 # Split into (path,file).
1463 res_parts = os.path.split(res)
1464
1465 # Now split the path into (prefix,maybe.lproj).
1466 lproj_parts = os.path.split(res_parts[0])
1467 # If the resource lives in a .lproj bundle, add that to the destination.
1468 if lproj_parts[1].endswith('.lproj'):
1469 output = os.path.join(output, lproj_parts[1])
1470
1471 output = os.path.join(output, res_parts[1])
1472 # Compiled XIB files are referred to by .nib.
1473 if output.endswith('.xib'):
1474 output = os.path.splitext(output)[0] + '.nib'
1475 # Compiled storyboard files are referred to by .storyboardc.
1476 if output.endswith('.storyboard'):
1477 output = os.path.splitext(output)[0] + '.storyboardc'
1478
1479 yield output, res
1480
1481
1482def GetMacInfoPlist(product_dir, xcode_settings, gyp_path_to_build_path):
1483 """Returns (info_plist, dest_plist, defines, extra_env), where:
1484 * |info_plist| is the source plist path, relative to the
1485 build directory,
1486 * |dest_plist| is the destination plist path, relative to the
1487 build directory,
1488 * |defines| is a list of preprocessor defines (empty if the plist
1489 shouldn't be preprocessed,
1490 * |extra_env| is a dict of env variables that should be exported when
1491 invoking |mac_tool copy-info-plist|.
1492
1493 Only call this for mac bundle targets.
1494
1495 Args:
1496 product_dir: Path to the directory containing the output bundle,
1497 relative to the build directory.
1498 xcode_settings: The XcodeSettings of the current target.
1499 gyp_to_build_path: A function that converts paths relative to the
1500 current gyp file to paths relative to the build direcotry.
1501 """
1502 info_plist = xcode_settings.GetPerTargetSetting('INFOPLIST_FILE')
1503 if not info_plist:
1504 return None, None, [], {}
1505
1506 # The make generator doesn't support it, so forbid it everywhere
1507 # to keep the generators more interchangable.
1508 assert ' ' not in info_plist, (
1509 "Spaces in Info.plist filenames not supported (%s)" % info_plist)
1510
1511 info_plist = gyp_path_to_build_path(info_plist)
1512
1513 # If explicitly set to preprocess the plist, invoke the C preprocessor and
1514 # specify any defines as -D flags.
1515 if xcode_settings.GetPerTargetSetting(
1516 'INFOPLIST_PREPROCESS', default='NO') == 'YES':
1517 # Create an intermediate file based on the path.
1518 defines = shlex.split(xcode_settings.GetPerTargetSetting(
1519 'INFOPLIST_PREPROCESSOR_DEFINITIONS', default=''))
1520 else:
1521 defines = []
1522
1523 dest_plist = os.path.join(product_dir, xcode_settings.GetBundlePlistPath())
1524 extra_env = xcode_settings.GetPerTargetSettings()
1525
1526 return info_plist, dest_plist, defines, extra_env
1527
1528
1529def _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
1530 additional_settings=None):
1531 """Return the environment variables that Xcode would set. See
1532 http://developer.apple.com/library/mac/#documentation/DeveloperTools/Reference/XcodeBuildSettingRef/1-Build_Setting_Reference/build_setting_ref.html#//apple_ref/doc/uid/TP40003931-CH3-SW153
1533 for a full list.
1534
1535 Args:
1536 xcode_settings: An XcodeSettings object. If this is None, this function
1537 returns an empty dict.
1538 built_products_dir: Absolute path to the built products dir.
1539 srcroot: Absolute path to the source root.
1540 configuration: The build configuration name.
1541 additional_settings: An optional dict with more values to add to the
1542 result.
1543 """
1544 if not xcode_settings: return {}
1545
1546 # This function is considered a friend of XcodeSettings, so let it reach into
1547 # its implementation details.
1548 spec = xcode_settings.spec
1549
1550 # These are filled in on a as-needed basis.
1551 env = {
1552 'BUILT_FRAMEWORKS_DIR' : built_products_dir,
1553 'BUILT_PRODUCTS_DIR' : built_products_dir,
1554 'CONFIGURATION' : configuration,
1555 'PRODUCT_NAME' : xcode_settings.GetProductName(),
1556 # See /Developer/Platforms/MacOSX.platform/Developer/Library/Xcode/Specifications/MacOSX\ Product\ Types.xcspec for FULL_PRODUCT_NAME
1557 'SRCROOT' : srcroot,
1558 'SOURCE_ROOT': '${SRCROOT}',
1559 # This is not true for static libraries, but currently the env is only
1560 # written for bundles:
1561 'TARGET_BUILD_DIR' : built_products_dir,
1562 'TEMP_DIR' : '${TMPDIR}',
1563 'XCODE_VERSION_ACTUAL' : XcodeVersion()[0],
1564 }
1565 if xcode_settings.GetPerConfigSetting('SDKROOT', configuration):
1566 env['SDKROOT'] = xcode_settings._SdkPath(configuration)
1567 else:
1568 env['SDKROOT'] = ''
1569
1570 if xcode_settings.mac_toolchain_dir:
1571 env['DEVELOPER_DIR'] = xcode_settings.mac_toolchain_dir
1572
1573 if spec['type'] in (
1574 'executable', 'static_library', 'shared_library', 'loadable_module'):
1575 env['EXECUTABLE_NAME'] = xcode_settings.GetExecutableName()
1576 env['EXECUTABLE_PATH'] = xcode_settings.GetExecutablePath()
1577 env['FULL_PRODUCT_NAME'] = xcode_settings.GetFullProductName()
1578 mach_o_type = xcode_settings.GetMachOType()
1579 if mach_o_type:
1580 env['MACH_O_TYPE'] = mach_o_type
1581 env['PRODUCT_TYPE'] = xcode_settings.GetProductType()
1582 if xcode_settings._IsBundle():
1583 env['CONTENTS_FOLDER_PATH'] = \
1584 xcode_settings.GetBundleContentsFolderPath()
1585 env['UNLOCALIZED_RESOURCES_FOLDER_PATH'] = \
1586 xcode_settings.GetBundleResourceFolder()
1587 env['INFOPLIST_PATH'] = xcode_settings.GetBundlePlistPath()
1588 env['WRAPPER_NAME'] = xcode_settings.GetWrapperName()
1589
1590 install_name = xcode_settings.GetInstallName()
1591 if install_name:
1592 env['LD_DYLIB_INSTALL_NAME'] = install_name
1593 install_name_base = xcode_settings.GetInstallNameBase()
1594 if install_name_base:
1595 env['DYLIB_INSTALL_NAME_BASE'] = install_name_base
1596 if XcodeVersion() >= '0500' and not env.get('SDKROOT'):
1597 sdk_root = xcode_settings._SdkRoot(configuration)
1598 if not sdk_root:
1599 sdk_root = xcode_settings._XcodeSdkPath('')
1600 env['SDKROOT'] = sdk_root
1601
1602 if not additional_settings:
1603 additional_settings = {}
1604 else:
1605 # Flatten lists to strings.
1606 for k in additional_settings:
1607 if not isinstance(additional_settings[k], str):
1608 additional_settings[k] = ' '.join(additional_settings[k])
1609 additional_settings.update(env)
1610
1611 for k in additional_settings:
1612 additional_settings[k] = _NormalizeEnvVarReferences(additional_settings[k])
1613
1614 return additional_settings
1615
1616
1617def _NormalizeEnvVarReferences(str):
1618 """Takes a string containing variable references in the form ${FOO}, $(FOO),
1619 or $FOO, and returns a string with all variable references in the form ${FOO}.
1620 """
1621 # $FOO -> ${FOO}
1622 str = re.sub(r'\$([a-zA-Z_][a-zA-Z0-9_]*)', r'${\1}', str)
1623
1624 # $(FOO) -> ${FOO}
1625 matches = re.findall(r'(\$\(([a-zA-Z0-9\-_]+)\))', str)
1626 for match in matches:
1627 to_replace, variable = match
1628 assert '$(' not in match, '$($(FOO)) variables not supported: ' + match
1629 str = str.replace(to_replace, '${' + variable + '}')
1630
1631 return str
1632
1633
1634def ExpandEnvVars(string, expansions):
1635 """Expands ${VARIABLES}, $(VARIABLES), and $VARIABLES in string per the
1636 expansions list. If the variable expands to something that references
1637 another variable, this variable is expanded as well if it's in env --
1638 until no variables present in env are left."""
1639 for k, v in reversed(expansions):
1640 string = string.replace('${' + k + '}', v)
1641 string = string.replace('$(' + k + ')', v)
1642 string = string.replace('$' + k, v)
1643 return string
1644
1645
1646def _TopologicallySortedEnvVarKeys(env):
1647 """Takes a dict |env| whose values are strings that can refer to other keys,
1648 for example env['foo'] = '$(bar) and $(baz)'. Returns a list L of all keys of
1649 env such that key2 is after key1 in L if env[key2] refers to env[key1].
1650
1651 Throws an Exception in case of dependency cycles.
1652 """
1653 # Since environment variables can refer to other variables, the evaluation
1654 # order is important. Below is the logic to compute the dependency graph
1655 # and sort it.
1656 regex = re.compile(r'\$\{([a-zA-Z0-9\-_]+)\}')
1657 def GetEdges(node):
1658 # Use a definition of edges such that user_of_variable -> used_varible.
1659 # This happens to be easier in this case, since a variable's
1660 # definition contains all variables it references in a single string.
1661 # We can then reverse the result of the topological sort at the end.
1662 # Since: reverse(topsort(DAG)) = topsort(reverse_edges(DAG))
1663 matches = set([v for v in regex.findall(env[node]) if v in env])
1664 for dependee in matches:
1665 assert '${' not in dependee, 'Nested variables not supported: ' + dependee
1666 return matches
1667
1668 try:
1669 # Topologically sort, and then reverse, because we used an edge definition
1670 # that's inverted from the expected result of this function (see comment
1671 # above).
1672 order = gyp.common.TopologicallySorted(env.keys(), GetEdges)
1673 order.reverse()
1674 return order
1675 except gyp.common.CycleError, e:
1676 raise GypError(
1677 'Xcode environment variables are cyclically dependent: ' + str(e.nodes))
1678
1679
1680def GetSortedXcodeEnv(xcode_settings, built_products_dir, srcroot,
1681 configuration, additional_settings=None):
1682 env = _GetXcodeEnv(xcode_settings, built_products_dir, srcroot, configuration,
1683 additional_settings)
1684 return [(key, env[key]) for key in _TopologicallySortedEnvVarKeys(env)]
1685
1686
1687def GetSpecPostbuildCommands(spec, quiet=False):
1688 """Returns the list of postbuilds explicitly defined on |spec|, in a form
1689 executable by a shell."""
1690 postbuilds = []
1691 for postbuild in spec.get('postbuilds', []):
1692 if not quiet:
1693 postbuilds.append('echo POSTBUILD\\(%s\\) %s' % (
1694 spec['target_name'], postbuild['postbuild_name']))
1695 postbuilds.append(gyp.common.EncodePOSIXShellList(postbuild['action']))
1696 return postbuilds
1697
1698
1699def _HasIOSTarget(targets):
1700 """Returns true if any target contains the iOS specific key
1701 IPHONEOS_DEPLOYMENT_TARGET."""
1702 for target_dict in targets.values():
1703 for config in target_dict['configurations'].values():
1704 if config.get('xcode_settings', {}).get('IPHONEOS_DEPLOYMENT_TARGET'):
1705 return True
1706 return False
1707
1708
1709def _AddIOSDeviceConfigurations(targets):
1710 """Clone all targets and append -iphoneos to the name. Configure these targets
1711 to build for iOS devices and use correct architectures for those builds."""
1712 for target_dict in targets.itervalues():
1713 toolset = target_dict['toolset']
1714 configs = target_dict['configurations']
1715 for config_name, config_dict in dict(configs).iteritems():
1716 iphoneos_config_dict = copy.deepcopy(config_dict)
1717 configs[config_name + '-iphoneos'] = iphoneos_config_dict
1718 configs[config_name + '-iphonesimulator'] = config_dict
1719 if toolset == 'target':
1720 iphoneos_config_dict['xcode_settings']['SDKROOT'] = 'iphoneos'
1721 return targets
1722
1723def CloneConfigurationForDeviceAndEmulator(target_dicts):
1724 """If |target_dicts| contains any iOS targets, automatically create -iphoneos
1725 targets for iOS device builds."""
1726 if _HasIOSTarget(target_dicts):
1727 return _AddIOSDeviceConfigurations(target_dicts)
1728 return target_dicts