Completed
Branch rebuild (e7a2d2)
by Glenn
08:26
created

XcodeSettings   F

Complexity

Total Complexity 252

Size/Duplication

Total Lines 988
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
wmc 252
dl 0
loc 988
c 0
b 0
f 0
rs 1.263

How to fix   Complexity   

Complex Class

Complex classes like XcodeSettings often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

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