1from __future__ import print_function
2# Copyright (c) 2012 Google Inc. All rights reserved.
3# Use of this source code is governed by a BSD-style license that can be
4# found in the LICENSE file.
5
6import filecmp
7import gyp.common
8import gyp.xcodeproj_file
9import gyp.xcode_ninja
10import errno
11import os
12import sys
13import posixpath
14import re
15import shutil
16import subprocess
17import tempfile
18
19
20# Project files generated by this module will use _intermediate_var as a
21# custom Xcode setting whose value is a DerivedSources-like directory that's
22# project-specific and configuration-specific.  The normal choice,
23# DERIVED_FILE_DIR, is target-specific, which is thought to be too restrictive
24# as it is likely that multiple targets within a single project file will want
25# to access the same set of generated files.  The other option,
26# PROJECT_DERIVED_FILE_DIR, is unsuitable because while it is project-specific,
27# it is not configuration-specific.  INTERMEDIATE_DIR is defined as
28# $(PROJECT_DERIVED_FILE_DIR)/$(CONFIGURATION).
29_intermediate_var = 'INTERMEDIATE_DIR'
30
31# SHARED_INTERMEDIATE_DIR is the same, except that it is shared among all
32# targets that share the same BUILT_PRODUCTS_DIR.
33_shared_intermediate_var = 'SHARED_INTERMEDIATE_DIR'
34
35_library_search_paths_var = 'LIBRARY_SEARCH_PATHS'
36
37generator_default_variables = {
38  'EXECUTABLE_PREFIX': '',
39  'EXECUTABLE_SUFFIX': '',
40  'STATIC_LIB_PREFIX': 'lib',
41  'SHARED_LIB_PREFIX': 'lib',
42  'STATIC_LIB_SUFFIX': '.a',
43  'SHARED_LIB_SUFFIX': '.dylib',
44  # INTERMEDIATE_DIR is a place for targets to build up intermediate products.
45  # It is specific to each build environment.  It is only guaranteed to exist
46  # and be constant within the context of a project, corresponding to a single
47  # input file.  Some build environments may allow their intermediate directory
48  # to be shared on a wider scale, but this is not guaranteed.
49  'INTERMEDIATE_DIR': '$(%s)' % _intermediate_var,
50  'OS': 'mac',
51  'PRODUCT_DIR': '$(BUILT_PRODUCTS_DIR)',
52  'LIB_DIR': '$(BUILT_PRODUCTS_DIR)',
53  'RULE_INPUT_ROOT': '$(INPUT_FILE_BASE)',
54  'RULE_INPUT_EXT': '$(INPUT_FILE_SUFFIX)',
55  'RULE_INPUT_NAME': '$(INPUT_FILE_NAME)',
56  'RULE_INPUT_PATH': '$(INPUT_FILE_PATH)',
57  'RULE_INPUT_DIRNAME': '$(INPUT_FILE_DIRNAME)',
58  'SHARED_INTERMEDIATE_DIR': '$(%s)' % _shared_intermediate_var,
59  'CONFIGURATION_NAME': '$(CONFIGURATION)',
60}
61
62# The Xcode-specific sections that hold paths.
63generator_additional_path_sections = [
64  'mac_bundle_resources',
65  'mac_framework_headers',
66  'mac_framework_private_headers',
67  # 'mac_framework_dirs', input already handles _dirs endings.
68]
69
70# The Xcode-specific keys that exist on targets and aren't moved down to
71# configurations.
72generator_additional_non_configuration_keys = [
73  'ios_app_extension',
74  'ios_watch_app',
75  'ios_watchkit_extension',
76  'mac_bundle',
77  'mac_bundle_resources',
78  'mac_framework_headers',
79  'mac_framework_private_headers',
80  'mac_xctest_bundle',
81  'xcode_create_dependents_test_runner',
82]
83
84# We want to let any rules apply to files that are resources also.
85generator_extra_sources_for_rules = [
86  'mac_bundle_resources',
87  'mac_framework_headers',
88  'mac_framework_private_headers',
89]
90
91generator_filelist_paths = None
92
93# Xcode's standard set of library directories, which don't need to be duplicated
94# in LIBRARY_SEARCH_PATHS. This list is not exhaustive, but that's okay.
95xcode_standard_library_dirs = frozenset([
96  '$(SDKROOT)/usr/lib',
97  '$(SDKROOT)/usr/local/lib',
98])
99
100def CreateXCConfigurationList(configuration_names):
101  xccl = gyp.xcodeproj_file.XCConfigurationList({'buildConfigurations': []})
102  if len(configuration_names) == 0:
103    configuration_names = ['Default']
104  for configuration_name in configuration_names:
105    xcbc = gyp.xcodeproj_file.XCBuildConfiguration({
106        'name': configuration_name})
107    xccl.AppendProperty('buildConfigurations', xcbc)
108  xccl.SetProperty('defaultConfigurationName', configuration_names[0])
109  return xccl
110
111
112class XcodeProject(object):
113  def __init__(self, gyp_path, path, build_file_dict):
114    self.gyp_path = gyp_path
115    self.path = path
116    self.project = gyp.xcodeproj_file.PBXProject(path=path)
117    projectDirPath = gyp.common.RelativePath(
118                         os.path.dirname(os.path.abspath(self.gyp_path)),
119                         os.path.dirname(path) or '.')
120    self.project.SetProperty('projectDirPath', projectDirPath)
121    self.project_file = \
122        gyp.xcodeproj_file.XCProjectFile({'rootObject': self.project})
123    self.build_file_dict = build_file_dict
124
125    # TODO(mark): add destructor that cleans up self.path if created_dir is
126    # True and things didn't complete successfully.  Or do something even
127    # better with "try"?
128    self.created_dir = False
129    try:
130      os.makedirs(self.path)
131      self.created_dir = True
132    except OSError as e:
133      if e.errno != errno.EEXIST:
134        raise
135
136  def Finalize1(self, xcode_targets, serialize_all_tests):
137    # Collect a list of all of the build configuration names used by the
138    # various targets in the file.  It is very heavily advised to keep each
139    # target in an entire project (even across multiple project files) using
140    # the same set of configuration names.
141    configurations = []
142    for xct in self.project.GetProperty('targets'):
143      xccl = xct.GetProperty('buildConfigurationList')
144      xcbcs = xccl.GetProperty('buildConfigurations')
145      for xcbc in xcbcs:
146        name = xcbc.GetProperty('name')
147        if name not in configurations:
148          configurations.append(name)
149
150    # Replace the XCConfigurationList attached to the PBXProject object with
151    # a new one specifying all of the configuration names used by the various
152    # targets.
153    try:
154      xccl = CreateXCConfigurationList(configurations)
155      self.project.SetProperty('buildConfigurationList', xccl)
156    except:
157      sys.stderr.write("Problem with gyp file %s\n" % self.gyp_path)
158      raise
159
160    # The need for this setting is explained above where _intermediate_var is
161    # defined.  The comments below about wanting to avoid project-wide build
162    # settings apply here too, but this needs to be set on a project-wide basis
163    # so that files relative to the _intermediate_var setting can be displayed
164    # properly in the Xcode UI.
165    #
166    # Note that for configuration-relative files such as anything relative to
167    # _intermediate_var, for the purposes of UI tree view display, Xcode will
168    # only resolve the configuration name once, when the project file is
169    # opened.  If the active build configuration is changed, the project file
170    # must be closed and reopened if it is desired for the tree view to update.
171    # This is filed as Apple radar 6588391.
172    xccl.SetBuildSetting(_intermediate_var,
173                         '$(PROJECT_DERIVED_FILE_DIR)/$(CONFIGURATION)')
174    xccl.SetBuildSetting(_shared_intermediate_var,
175                         '$(SYMROOT)/DerivedSources/$(CONFIGURATION)')
176
177    # Set user-specified project-wide build settings and config files.  This
178    # is intended to be used very sparingly.  Really, almost everything should
179    # go into target-specific build settings sections.  The project-wide
180    # settings are only intended to be used in cases where Xcode attempts to
181    # resolve variable references in a project context as opposed to a target
182    # context, such as when resolving sourceTree references while building up
183    # the tree tree view for UI display.
184    # Any values set globally are applied to all configurations, then any
185    # per-configuration values are applied.
186    for xck, xcv in self.build_file_dict.get('xcode_settings', {}).items():
187      xccl.SetBuildSetting(xck, xcv)
188    if 'xcode_config_file' in self.build_file_dict:
189      config_ref = self.project.AddOrGetFileInRootGroup(
190          self.build_file_dict['xcode_config_file'])
191      xccl.SetBaseConfiguration(config_ref)
192    build_file_configurations = self.build_file_dict.get('configurations', {})
193    if build_file_configurations:
194      for config_name in configurations:
195        build_file_configuration_named = \
196            build_file_configurations.get(config_name, {})
197        if build_file_configuration_named:
198          xcc = xccl.ConfigurationNamed(config_name)
199          for xck, xcv in build_file_configuration_named.get('xcode_settings',
200                                                             {}).items():
201            xcc.SetBuildSetting(xck, xcv)
202          if 'xcode_config_file' in build_file_configuration_named:
203            config_ref = self.project.AddOrGetFileInRootGroup(
204                build_file_configurations[config_name]['xcode_config_file'])
205            xcc.SetBaseConfiguration(config_ref)
206
207    # Sort the targets based on how they appeared in the input.
208    # TODO(mark): Like a lot of other things here, this assumes internal
209    # knowledge of PBXProject - in this case, of its "targets" property.
210
211    # ordinary_targets are ordinary targets that are already in the project
212    # file. run_test_targets are the targets that run unittests and should be
213    # used for the Run All Tests target.  support_targets are the action/rule
214    # targets used by GYP file targets, just kept for the assert check.
215    ordinary_targets = []
216    run_test_targets = []
217    support_targets = []
218
219    # targets is full list of targets in the project.
220    targets = []
221
222    # does the it define it's own "all"?
223    has_custom_all = False
224
225    # targets_for_all is the list of ordinary_targets that should be listed
226    # in this project's "All" target.  It includes each non_runtest_target
227    # that does not have suppress_wildcard set.
228    targets_for_all = []
229
230    for target in self.build_file_dict['targets']:
231      target_name = target['target_name']
232      toolset = target['toolset']
233      qualified_target = gyp.common.QualifiedTarget(self.gyp_path, target_name,
234                                                    toolset)
235      xcode_target = xcode_targets[qualified_target]
236      # Make sure that the target being added to the sorted list is already in
237      # the unsorted list.
238      assert xcode_target in self.project._properties['targets']
239      targets.append(xcode_target)
240      ordinary_targets.append(xcode_target)
241      if xcode_target.support_target:
242        support_targets.append(xcode_target.support_target)
243        targets.append(xcode_target.support_target)
244
245      if not int(target.get('suppress_wildcard', False)):
246        targets_for_all.append(xcode_target)
247
248      if target_name.lower() == 'all':
249        has_custom_all = True
250
251      # If this target has a 'run_as' attribute, add its target to the
252      # targets, and add it to the test targets.
253      if target.get('run_as'):
254        # Make a target to run something.  It should have one
255        # dependency, the parent xcode target.
256        xccl = CreateXCConfigurationList(configurations)
257        run_target = gyp.xcodeproj_file.PBXAggregateTarget({
258              'name':                   'Run ' + target_name,
259              'productName':            xcode_target.GetProperty('productName'),
260              'buildConfigurationList': xccl,
261            },
262            parent=self.project)
263        run_target.AddDependency(xcode_target)
264
265        command = target['run_as']
266        script = ''
267        if command.get('working_directory'):
268          script = script + 'cd "%s"\n' % \
269                   gyp.xcodeproj_file.ConvertVariablesToShellSyntax(
270                       command.get('working_directory'))
271
272        if command.get('environment'):
273          script = script + "\n".join(
274            ['export %s="%s"' %
275             (key, gyp.xcodeproj_file.ConvertVariablesToShellSyntax(val))
276             for (key, val) in command.get('environment').items()]) + "\n"
277
278        # Some test end up using sockets, files on disk, etc. and can get
279        # confused if more then one test runs at a time.  The generator
280        # flag 'xcode_serialize_all_test_runs' controls the forcing of all
281        # tests serially.  It defaults to True.  To get serial runs this
282        # little bit of python does the same as the linux flock utility to
283        # make sure only one runs at a time.
284        command_prefix = ''
285        if serialize_all_tests:
286          command_prefix = \
287"""python -c "import fcntl, subprocess, sys
288file = open('$TMPDIR/GYP_serialize_test_runs', 'a')
289fcntl.flock(file.fileno(), fcntl.LOCK_EX)
290sys.exit(subprocess.call(sys.argv[1:]))" """
291
292        # If we were unable to exec for some reason, we want to exit
293        # with an error, and fixup variable references to be shell
294        # syntax instead of xcode syntax.
295        script = script + 'exec ' + command_prefix + '%s\nexit 1\n' % \
296                 gyp.xcodeproj_file.ConvertVariablesToShellSyntax(
297                     gyp.common.EncodePOSIXShellList(command.get('action')))
298
299        ssbp = gyp.xcodeproj_file.PBXShellScriptBuildPhase({
300              'shellScript':      script,
301              'showEnvVarsInLog': 0,
302            })
303        run_target.AppendProperty('buildPhases', ssbp)
304
305        # Add the run target to the project file.
306        targets.append(run_target)
307        run_test_targets.append(run_target)
308        xcode_target.test_runner = run_target
309
310
311    # Make sure that the list of targets being replaced is the same length as
312    # the one replacing it, but allow for the added test runner targets.
313    assert len(self.project._properties['targets']) == \
314      len(ordinary_targets) + len(support_targets)
315
316    self.project._properties['targets'] = targets
317
318    # Get rid of unnecessary levels of depth in groups like the Source group.
319    self.project.RootGroupsTakeOverOnlyChildren(True)
320
321    # Sort the groups nicely.  Do this after sorting the targets, because the
322    # Products group is sorted based on the order of the targets.
323    self.project.SortGroups()
324
325    # Create an "All" target if there's more than one target in this project
326    # file and the project didn't define its own "All" target.  Put a generated
327    # "All" target first so that people opening up the project for the first
328    # time will build everything by default.
329    if len(targets_for_all) > 1 and not has_custom_all:
330      xccl = CreateXCConfigurationList(configurations)
331      all_target = gyp.xcodeproj_file.PBXAggregateTarget(
332          {
333            'buildConfigurationList': xccl,
334            'name':                   'All',
335          },
336          parent=self.project)
337
338      for target in targets_for_all:
339        all_target.AddDependency(target)
340
341      # TODO(mark): This is evil because it relies on internal knowledge of
342      # PBXProject._properties.  It's important to get the "All" target first,
343      # though.
344      self.project._properties['targets'].insert(0, all_target)
345
346    # The same, but for run_test_targets.
347    if len(run_test_targets) > 1:
348      xccl = CreateXCConfigurationList(configurations)
349      run_all_tests_target = gyp.xcodeproj_file.PBXAggregateTarget(
350          {
351            'buildConfigurationList': xccl,
352            'name':                   'Run All Tests',
353          },
354          parent=self.project)
355      for run_test_target in run_test_targets:
356        run_all_tests_target.AddDependency(run_test_target)
357
358      # Insert after the "All" target, which must exist if there is more than
359      # one run_test_target.
360      self.project._properties['targets'].insert(1, run_all_tests_target)
361
362  def Finalize2(self, xcode_targets, xcode_target_to_target_dict):
363    # Finalize2 needs to happen in a separate step because the process of
364    # updating references to other projects depends on the ordering of targets
365    # within remote project files.  Finalize1 is responsible for sorting duty,
366    # and once all project files are sorted, Finalize2 can come in and update
367    # these references.
368
369    # To support making a "test runner" target that will run all the tests
370    # that are direct dependents of any given target, we look for
371    # xcode_create_dependents_test_runner being set on an Aggregate target,
372    # and generate a second target that will run the tests runners found under
373    # the marked target.
374    for bf_tgt in self.build_file_dict['targets']:
375      if int(bf_tgt.get('xcode_create_dependents_test_runner', 0)):
376        tgt_name = bf_tgt['target_name']
377        toolset = bf_tgt['toolset']
378        qualified_target = gyp.common.QualifiedTarget(self.gyp_path,
379                                                      tgt_name, toolset)
380        xcode_target = xcode_targets[qualified_target]
381        if isinstance(xcode_target, gyp.xcodeproj_file.PBXAggregateTarget):
382          # Collect all the run test targets.
383          all_run_tests = []
384          pbxtds = xcode_target.GetProperty('dependencies')
385          for pbxtd in pbxtds:
386            pbxcip = pbxtd.GetProperty('targetProxy')
387            dependency_xct = pbxcip.GetProperty('remoteGlobalIDString')
388            if hasattr(dependency_xct, 'test_runner'):
389              all_run_tests.append(dependency_xct.test_runner)
390
391          # Directly depend on all the runners as they depend on the target
392          # that builds them.
393          if len(all_run_tests) > 0:
394            run_all_target = gyp.xcodeproj_file.PBXAggregateTarget({
395                  'name':        'Run %s Tests' % tgt_name,
396                  'productName': tgt_name,
397                },
398                parent=self.project)
399            for run_test_target in all_run_tests:
400              run_all_target.AddDependency(run_test_target)
401
402            # Insert the test runner after the related target.
403            idx = self.project._properties['targets'].index(xcode_target)
404            self.project._properties['targets'].insert(idx + 1, run_all_target)
405
406    # Update all references to other projects, to make sure that the lists of
407    # remote products are complete.  Otherwise, Xcode will fill them in when
408    # it opens the project file, which will result in unnecessary diffs.
409    # TODO(mark): This is evil because it relies on internal knowledge of
410    # PBXProject._other_pbxprojects.
411    for other_pbxproject in self.project._other_pbxprojects.keys():
412      self.project.AddOrGetProjectReference(other_pbxproject)
413
414    self.project.SortRemoteProductReferences()
415
416    # Give everything an ID.
417    self.project_file.ComputeIDs()
418
419    # Make sure that no two objects in the project file have the same ID.  If
420    # multiple objects wind up with the same ID, upon loading the file, Xcode
421    # will only recognize one object (the last one in the file?) and the
422    # results are unpredictable.
423    self.project_file.EnsureNoIDCollisions()
424
425  def Write(self):
426    # Write the project file to a temporary location first.  Xcode watches for
427    # changes to the project file and presents a UI sheet offering to reload
428    # the project when it does change.  However, in some cases, especially when
429    # multiple projects are open or when Xcode is busy, things don't work so
430    # seamlessly.  Sometimes, Xcode is able to detect that a project file has
431    # changed but can't unload it because something else is referencing it.
432    # To mitigate this problem, and to avoid even having Xcode present the UI
433    # sheet when an open project is rewritten for inconsequential changes, the
434    # project file is written to a temporary file in the xcodeproj directory
435    # first.  The new temporary file is then compared to the existing project
436    # file, if any.  If they differ, the new file replaces the old; otherwise,
437    # the new project file is simply deleted.  Xcode properly detects a file
438    # being renamed over an open project file as a change and so it remains
439    # able to present the "project file changed" sheet under this system.
440    # Writing to a temporary file first also avoids the possible problem of
441    # Xcode rereading an incomplete project file.
442    (output_fd, new_pbxproj_path) = \
443        tempfile.mkstemp(suffix='.tmp', prefix='project.pbxproj.gyp.',
444                         dir=self.path)
445
446    try:
447      output_file = os.fdopen(output_fd, 'wb')
448
449      self.project_file.Print(output_file)
450      output_file.close()
451
452      pbxproj_path = os.path.join(self.path, 'project.pbxproj')
453
454      same = False
455      try:
456        same = filecmp.cmp(pbxproj_path, new_pbxproj_path, False)
457      except OSError as e:
458        if e.errno != errno.ENOENT:
459          raise
460
461      if same:
462        # The new file is identical to the old one, just get rid of the new
463        # one.
464        os.unlink(new_pbxproj_path)
465      else:
466        # The new file is different from the old one, or there is no old one.
467        # Rename the new file to the permanent name.
468        #
469        # tempfile.mkstemp uses an overly restrictive mode, resulting in a
470        # file that can only be read by the owner, regardless of the umask.
471        # There's no reason to not respect the umask here, which means that
472        # an extra hoop is required to fetch it and reset the new file's mode.
473        #
474        # No way to get the umask without setting a new one?  Set a safe one
475        # and then set it back to the old value.
476        umask = os.umask(0o77)
477        os.umask(umask)
478
479        os.chmod(new_pbxproj_path, 0o666 & ~umask)
480        os.rename(new_pbxproj_path, pbxproj_path)
481
482    except Exception:
483      # Don't leave turds behind.  In fact, if this code was responsible for
484      # creating the xcodeproj directory, get rid of that too.
485      os.unlink(new_pbxproj_path)
486      if self.created_dir:
487        shutil.rmtree(self.path, True)
488      raise
489
490
491def AddSourceToTarget(source, type, pbxp, xct):
492  # TODO(mark): Perhaps source_extensions and library_extensions can be made a
493  # little bit fancier.
494  source_extensions = ['c', 'cc', 'cpp', 'cxx', 'm', 'mm', 's', 'swift']
495
496  # .o is conceptually more of a "source" than a "library," but Xcode thinks
497  # of "sources" as things to compile and "libraries" (or "frameworks") as
498  # things to link with. Adding an object file to an Xcode target's frameworks
499  # phase works properly.
500  library_extensions = ['a', 'dylib', 'framework', 'o']
501
502  basename = posixpath.basename(source)
503  (root, ext) = posixpath.splitext(basename)
504  if ext:
505    ext = ext[1:].lower()
506
507  if ext in source_extensions and type != 'none':
508    xct.SourcesPhase().AddFile(source)
509  elif ext in library_extensions and type != 'none':
510    xct.FrameworksPhase().AddFile(source)
511  else:
512    # Files that aren't added to a sources or frameworks build phase can still
513    # go into the project file, just not as part of a build phase.
514    pbxp.AddOrGetFileInRootGroup(source)
515
516
517def AddResourceToTarget(resource, pbxp, xct):
518  # TODO(mark): Combine with AddSourceToTarget above?  Or just inline this call
519  # where it's used.
520  xct.ResourcesPhase().AddFile(resource)
521
522
523def AddHeaderToTarget(header, pbxp, xct, is_public):
524  # TODO(mark): Combine with AddSourceToTarget above?  Or just inline this call
525  # where it's used.
526  settings = '{ATTRIBUTES = (%s, ); }' % ('Private', 'Public')[is_public]
527  xct.HeadersPhase().AddFile(header, settings)
528
529
530_xcode_variable_re = re.compile(r'(\$\((.*?)\))')
531def ExpandXcodeVariables(string, expansions):
532  """Expands Xcode-style $(VARIABLES) in string per the expansions dict.
533
534  In some rare cases, it is appropriate to expand Xcode variables when a
535  project file is generated.  For any substring $(VAR) in string, if VAR is a
536  key in the expansions dict, $(VAR) will be replaced with expansions[VAR].
537  Any $(VAR) substring in string for which VAR is not a key in the expansions
538  dict will remain in the returned string.
539  """
540
541  matches = _xcode_variable_re.findall(string)
542  if matches is None:
543    return string
544
545  matches.reverse()
546  for match in matches:
547    (to_replace, variable) = match
548    if not variable in expansions:
549      continue
550
551    replacement = expansions[variable]
552    string = re.sub(re.escape(to_replace), replacement, string)
553
554  return string
555
556
557_xcode_define_re = re.compile(r'([\\\"\' ])')
558def EscapeXcodeDefine(s):
559  """We must escape the defines that we give to XCode so that it knows not to
560     split on spaces and to respect backslash and quote literals. However, we
561     must not quote the define, or Xcode will incorrectly intepret variables
562     especially $(inherited)."""
563  return re.sub(_xcode_define_re, r'\\\1', s)
564
565
566def PerformBuild(data, configurations, params):
567  options = params['options']
568
569  for build_file, build_file_dict in data.items():
570    (build_file_root, build_file_ext) = os.path.splitext(build_file)
571    if build_file_ext != '.gyp':
572      continue
573    xcodeproj_path = build_file_root + options.suffix + '.xcodeproj'
574    if options.generator_output:
575      xcodeproj_path = os.path.join(options.generator_output, xcodeproj_path)
576
577  for config in configurations:
578    arguments = ['xcodebuild', '-project', xcodeproj_path]
579    arguments += ['-configuration', config]
580    print("Building [%s]: %s" % (config, arguments))
581    subprocess.check_call(arguments)
582
583
584def CalculateGeneratorInputInfo(params):
585  toplevel = params['options'].toplevel_dir
586  if params.get('flavor') == 'ninja':
587    generator_dir = os.path.relpath(params['options'].generator_output or '.')
588    output_dir = params.get('generator_flags', {}).get('output_dir', 'out')
589    output_dir = os.path.normpath(os.path.join(generator_dir, output_dir))
590    qualified_out_dir = os.path.normpath(os.path.join(
591        toplevel, output_dir, 'gypfiles-xcode-ninja'))
592  else:
593    output_dir = os.path.normpath(os.path.join(toplevel, 'xcodebuild'))
594    qualified_out_dir = os.path.normpath(os.path.join(
595        toplevel, output_dir, 'gypfiles'))
596
597  global generator_filelist_paths
598  generator_filelist_paths = {
599      'toplevel': toplevel,
600      'qualified_out_dir': qualified_out_dir,
601  }
602
603
604def GenerateOutput(target_list, target_dicts, data, params):
605  # Optionally configure each spec to use ninja as the external builder.
606  ninja_wrapper = params.get('flavor') == 'ninja'
607  if ninja_wrapper:
608    (target_list, target_dicts, data) = \
609        gyp.xcode_ninja.CreateWrapper(target_list, target_dicts, data, params)
610
611  options = params['options']
612  generator_flags = params.get('generator_flags', {})
613  parallel_builds = generator_flags.get('xcode_parallel_builds', True)
614  serialize_all_tests = \
615      generator_flags.get('xcode_serialize_all_test_runs', True)
616  upgrade_check_project_version = \
617      generator_flags.get('xcode_upgrade_check_project_version', None)
618
619  # Format upgrade_check_project_version with leading zeros as needed.
620  if upgrade_check_project_version:
621    upgrade_check_project_version = str(upgrade_check_project_version)
622    while len(upgrade_check_project_version) < 4:
623      upgrade_check_project_version = '0' + upgrade_check_project_version
624
625  skip_excluded_files = \
626      not generator_flags.get('xcode_list_excluded_files', True)
627  xcode_projects = {}
628  for build_file, build_file_dict in data.items():
629    (build_file_root, build_file_ext) = os.path.splitext(build_file)
630    if build_file_ext != '.gyp':
631      continue
632    xcodeproj_path = build_file_root + options.suffix + '.xcodeproj'
633    if options.generator_output:
634      xcodeproj_path = os.path.join(options.generator_output, xcodeproj_path)
635    xcp = XcodeProject(build_file, xcodeproj_path, build_file_dict)
636    xcode_projects[build_file] = xcp
637    pbxp = xcp.project
638
639    # Set project-level attributes from multiple options
640    project_attributes = {}
641    if parallel_builds:
642      project_attributes['BuildIndependentTargetsInParallel'] = 'YES'
643    if upgrade_check_project_version:
644      project_attributes['LastUpgradeCheck'] = upgrade_check_project_version
645      project_attributes['LastTestingUpgradeCheck'] = \
646          upgrade_check_project_version
647      project_attributes['LastSwiftUpdateCheck'] = \
648          upgrade_check_project_version
649    pbxp.SetProperty('attributes', project_attributes)
650
651    # Add gyp/gypi files to project
652    if not generator_flags.get('standalone'):
653      main_group = pbxp.GetProperty('mainGroup')
654      build_group = gyp.xcodeproj_file.PBXGroup({'name': 'Build'})
655      main_group.AppendChild(build_group)
656      for included_file in build_file_dict['included_files']:
657        build_group.AddOrGetFileByPath(included_file, False)
658
659  xcode_targets = {}
660  xcode_target_to_target_dict = {}
661  for qualified_target in target_list:
662    [build_file, target_name, toolset] = \
663        gyp.common.ParseQualifiedTarget(qualified_target)
664
665    spec = target_dicts[qualified_target]
666    if spec['toolset'] != 'target':
667      raise Exception(
668          'Multiple toolsets not supported in xcode build (target %s)' %
669          qualified_target)
670    configuration_names = [spec['default_configuration']]
671    for configuration_name in sorted(spec['configurations'].keys()):
672      if configuration_name not in configuration_names:
673        configuration_names.append(configuration_name)
674    xcp = xcode_projects[build_file]
675    pbxp = xcp.project
676
677    # Set up the configurations for the target according to the list of names
678    # supplied.
679    xccl = CreateXCConfigurationList(configuration_names)
680
681    # Create an XCTarget subclass object for the target. The type with
682    # "+bundle" appended will be used if the target has "mac_bundle" set.
683    # loadable_modules not in a mac_bundle are mapped to
684    # com.googlecode.gyp.xcode.bundle, a pseudo-type that xcode.py interprets
685    # to create a single-file mh_bundle.
686    _types = {
687      'executable':                  'com.apple.product-type.tool',
688      'loadable_module':             'com.googlecode.gyp.xcode.bundle',
689      'shared_library':              'com.apple.product-type.library.dynamic',
690      'static_library':              'com.apple.product-type.library.static',
691      'mac_kernel_extension':        'com.apple.product-type.kernel-extension',
692      'executable+bundle':           'com.apple.product-type.application',
693      'loadable_module+bundle':      'com.apple.product-type.bundle',
694      'loadable_module+xctest':      'com.apple.product-type.bundle.unit-test',
695      'shared_library+bundle':       'com.apple.product-type.framework',
696      'executable+extension+bundle': 'com.apple.product-type.app-extension',
697      'executable+watch+extension+bundle':
698          'com.apple.product-type.watchkit-extension',
699      'executable+watch+bundle':
700          'com.apple.product-type.application.watchapp',
701      'mac_kernel_extension+bundle': 'com.apple.product-type.kernel-extension',
702    }
703
704    target_properties = {
705      'buildConfigurationList': xccl,
706      'name':                   target_name,
707    }
708
709    type = spec['type']
710    is_xctest = int(spec.get('mac_xctest_bundle', 0))
711    is_bundle = int(spec.get('mac_bundle', 0)) or is_xctest
712    is_app_extension = int(spec.get('ios_app_extension', 0))
713    is_watchkit_extension = int(spec.get('ios_watchkit_extension', 0))
714    is_watch_app = int(spec.get('ios_watch_app', 0))
715    if type != 'none':
716      type_bundle_key = type
717      if is_xctest:
718        type_bundle_key += '+xctest'
719        assert type == 'loadable_module', (
720            'mac_xctest_bundle targets must have type loadable_module '
721            '(target %s)' % target_name)
722      elif is_app_extension:
723        assert is_bundle, ('ios_app_extension flag requires mac_bundle '
724            '(target %s)' % target_name)
725        type_bundle_key += '+extension+bundle'
726      elif is_watchkit_extension:
727        assert is_bundle, ('ios_watchkit_extension flag requires mac_bundle '
728            '(target %s)' % target_name)
729        type_bundle_key += '+watch+extension+bundle'
730      elif is_watch_app:
731        assert is_bundle, ('ios_watch_app flag requires mac_bundle '
732            '(target %s)' % target_name)
733        type_bundle_key += '+watch+bundle'
734      elif is_bundle:
735        type_bundle_key += '+bundle'
736
737      xctarget_type = gyp.xcodeproj_file.PBXNativeTarget
738      try:
739        target_properties['productType'] = _types[type_bundle_key]
740      except KeyError as e:
741        gyp.common.ExceptionAppend(e, "-- unknown product type while "
742                                   "writing target %s" % target_name)
743        raise
744    else:
745      xctarget_type = gyp.xcodeproj_file.PBXAggregateTarget
746      assert not is_bundle, (
747          'mac_bundle targets cannot have type none (target "%s")' %
748          target_name)
749      assert not is_xctest, (
750          'mac_xctest_bundle targets cannot have type none (target "%s")' %
751          target_name)
752
753    target_product_name = spec.get('product_name')
754    if target_product_name is not None:
755      target_properties['productName'] = target_product_name
756
757    xct = xctarget_type(target_properties, parent=pbxp,
758                        force_outdir=spec.get('product_dir'),
759                        force_prefix=spec.get('product_prefix'),
760                        force_extension=spec.get('product_extension'))
761    pbxp.AppendProperty('targets', xct)
762    xcode_targets[qualified_target] = xct
763    xcode_target_to_target_dict[xct] = spec
764
765    spec_actions = spec.get('actions', [])
766    spec_rules = spec.get('rules', [])
767
768    # Xcode has some "issues" with checking dependencies for the "Compile
769    # sources" step with any source files/headers generated by actions/rules.
770    # To work around this, if a target is building anything directly (not
771    # type "none"), then a second target is used to run the GYP actions/rules
772    # and is made a dependency of this target.  This way the work is done
773    # before the dependency checks for what should be recompiled.
774    support_xct = None
775    # The Xcode "issues" don't affect xcode-ninja builds, since the dependency
776    # logic all happens in ninja.  Don't bother creating the extra targets in
777    # that case.
778    if type != 'none' and (spec_actions or spec_rules) and not ninja_wrapper:
779      support_xccl = CreateXCConfigurationList(configuration_names)
780      support_target_suffix = generator_flags.get(
781          'support_target_suffix', ' Support')
782      support_target_properties = {
783        'buildConfigurationList': support_xccl,
784        'name':                   target_name + support_target_suffix,
785      }
786      if target_product_name:
787        support_target_properties['productName'] = \
788            target_product_name + ' Support'
789      support_xct = \
790          gyp.xcodeproj_file.PBXAggregateTarget(support_target_properties,
791                                                parent=pbxp)
792      pbxp.AppendProperty('targets', support_xct)
793      xct.AddDependency(support_xct)
794    # Hang the support target off the main target so it can be tested/found
795    # by the generator during Finalize.
796    xct.support_target = support_xct
797
798    prebuild_index = 0
799
800    # Add custom shell script phases for "actions" sections.
801    for action in spec_actions:
802      # There's no need to write anything into the script to ensure that the
803      # output directories already exist, because Xcode will look at the
804      # declared outputs and automatically ensure that they exist for us.
805
806      # Do we have a message to print when this action runs?
807      message = action.get('message')
808      if message:
809        message = 'echo note: ' + gyp.common.EncodePOSIXShellArgument(message)
810      else:
811        message = ''
812
813      # Turn the list into a string that can be passed to a shell.
814      action_string = gyp.common.EncodePOSIXShellList(action['action'])
815
816      # Convert Xcode-type variable references to sh-compatible environment
817      # variable references.
818      message_sh = gyp.xcodeproj_file.ConvertVariablesToShellSyntax(message)
819      action_string_sh = gyp.xcodeproj_file.ConvertVariablesToShellSyntax(
820        action_string)
821
822      script = ''
823      # Include the optional message
824      if message_sh:
825        script += message_sh + '\n'
826      # Be sure the script runs in exec, and that if exec fails, the script
827      # exits signalling an error.
828      script += 'exec ' + action_string_sh + '\nexit 1\n'
829      ssbp = gyp.xcodeproj_file.PBXShellScriptBuildPhase({
830            'inputPaths': action['inputs'],
831            'name': 'Action "' + action['action_name'] + '"',
832            'outputPaths': action['outputs'],
833            'shellScript': script,
834            'showEnvVarsInLog': 0,
835          })
836
837      if support_xct:
838        support_xct.AppendProperty('buildPhases', ssbp)
839      else:
840        # TODO(mark): this assumes too much knowledge of the internals of
841        # xcodeproj_file; some of these smarts should move into xcodeproj_file
842        # itself.
843        xct._properties['buildPhases'].insert(prebuild_index, ssbp)
844        prebuild_index = prebuild_index + 1
845
846      # TODO(mark): Should verify that at most one of these is specified.
847      if int(action.get('process_outputs_as_sources', False)):
848        for output in action['outputs']:
849          AddSourceToTarget(output, type, pbxp, xct)
850
851      if int(action.get('process_outputs_as_mac_bundle_resources', False)):
852        for output in action['outputs']:
853          AddResourceToTarget(output, pbxp, xct)
854
855    # tgt_mac_bundle_resources holds the list of bundle resources so
856    # the rule processing can check against it.
857    if is_bundle:
858      tgt_mac_bundle_resources = spec.get('mac_bundle_resources', [])
859    else:
860      tgt_mac_bundle_resources = []
861
862    # Add custom shell script phases driving "make" for "rules" sections.
863    #
864    # Xcode's built-in rule support is almost powerful enough to use directly,
865    # but there are a few significant deficiencies that render them unusable.
866    # There are workarounds for some of its inadequacies, but in aggregate,
867    # the workarounds added complexity to the generator, and some workarounds
868    # actually require input files to be crafted more carefully than I'd like.
869    # Consequently, until Xcode rules are made more capable, "rules" input
870    # sections will be handled in Xcode output by shell script build phases
871    # performed prior to the compilation phase.
872    #
873    # The following problems with Xcode rules were found.  The numbers are
874    # Apple radar IDs.  I hope that these shortcomings are addressed, I really
875    # liked having the rules handled directly in Xcode during the period that
876    # I was prototyping this.
877    #
878    # 6588600 Xcode compiles custom script rule outputs too soon, compilation
879    #         fails.  This occurs when rule outputs from distinct inputs are
880    #         interdependent.  The only workaround is to put rules and their
881    #         inputs in a separate target from the one that compiles the rule
882    #         outputs.  This requires input file cooperation and it means that
883    #         process_outputs_as_sources is unusable.
884    # 6584932 Need to declare that custom rule outputs should be excluded from
885    #         compilation.  A possible workaround is to lie to Xcode about a
886    #         rule's output, giving it a dummy file it doesn't know how to
887    #         compile.  The rule action script would need to touch the dummy.
888    # 6584839 I need a way to declare additional inputs to a custom rule.
889    #         A possible workaround is a shell script phase prior to
890    #         compilation that touches a rule's primary input files if any
891    #         would-be additional inputs are newer than the output.  Modifying
892    #         the source tree - even just modification times - feels dirty.
893    # 6564240 Xcode "custom script" build rules always dump all environment
894    #         variables.  This is a low-prioroty problem and is not a
895    #         show-stopper.
896    rules_by_ext = {}
897    for rule in spec_rules:
898      rules_by_ext[rule['extension']] = rule
899
900      # First, some definitions:
901      #
902      # A "rule source" is a file that was listed in a target's "sources"
903      # list and will have a rule applied to it on the basis of matching the
904      # rule's "extensions" attribute.  Rule sources are direct inputs to
905      # rules.
906      #
907      # Rule definitions may specify additional inputs in their "inputs"
908      # attribute.  These additional inputs are used for dependency tracking
909      # purposes.
910      #
911      # A "concrete output" is a rule output with input-dependent variables
912      # resolved.  For example, given a rule with:
913      #   'extension': 'ext', 'outputs': ['$(INPUT_FILE_BASE).cc'],
914      # if the target's "sources" list contained "one.ext" and "two.ext",
915      # the "concrete output" for rule input "two.ext" would be "two.cc".  If
916      # a rule specifies multiple outputs, each input file that the rule is
917      # applied to will have the same number of concrete outputs.
918      #
919      # If any concrete outputs are outdated or missing relative to their
920      # corresponding rule_source or to any specified additional input, the
921      # rule action must be performed to generate the concrete outputs.
922
923      # concrete_outputs_by_rule_source will have an item at the same index
924      # as the rule['rule_sources'] that it corresponds to.  Each item is a
925      # list of all of the concrete outputs for the rule_source.
926      concrete_outputs_by_rule_source = []
927
928      # concrete_outputs_all is a flat list of all concrete outputs that this
929      # rule is able to produce, given the known set of input files
930      # (rule_sources) that apply to it.
931      concrete_outputs_all = []
932
933      # messages & actions are keyed by the same indices as rule['rule_sources']
934      # and concrete_outputs_by_rule_source.  They contain the message and
935      # action to perform after resolving input-dependent variables.  The
936      # message is optional, in which case None is stored for each rule source.
937      messages = []
938      actions = []
939
940      for rule_source in rule.get('rule_sources', []):
941        rule_source_dirname, rule_source_basename = \
942            posixpath.split(rule_source)
943        (rule_source_root, rule_source_ext) = \
944            posixpath.splitext(rule_source_basename)
945
946        # These are the same variable names that Xcode uses for its own native
947        # rule support.  Because Xcode's rule engine is not being used, they
948        # need to be expanded as they are written to the makefile.
949        rule_input_dict = {
950          'INPUT_FILE_BASE':   rule_source_root,
951          'INPUT_FILE_SUFFIX': rule_source_ext,
952          'INPUT_FILE_NAME':   rule_source_basename,
953          'INPUT_FILE_PATH':   rule_source,
954          'INPUT_FILE_DIRNAME': rule_source_dirname,
955        }
956
957        concrete_outputs_for_this_rule_source = []
958        for output in rule.get('outputs', []):
959          # Fortunately, Xcode and make both use $(VAR) format for their
960          # variables, so the expansion is the only transformation necessary.
961          # Any remaning $(VAR)-type variables in the string can be given
962          # directly to make, which will pick up the correct settings from
963          # what Xcode puts into the environment.
964          concrete_output = ExpandXcodeVariables(output, rule_input_dict)
965          concrete_outputs_for_this_rule_source.append(concrete_output)
966
967          # Add all concrete outputs to the project.
968          pbxp.AddOrGetFileInRootGroup(concrete_output)
969
970        concrete_outputs_by_rule_source.append( \
971            concrete_outputs_for_this_rule_source)
972        concrete_outputs_all.extend(concrete_outputs_for_this_rule_source)
973
974        # TODO(mark): Should verify that at most one of these is specified.
975        if int(rule.get('process_outputs_as_sources', False)):
976          for output in concrete_outputs_for_this_rule_source:
977            AddSourceToTarget(output, type, pbxp, xct)
978
979        # If the file came from the mac_bundle_resources list or if the rule
980        # is marked to process outputs as bundle resource, do so.
981        was_mac_bundle_resource = rule_source in tgt_mac_bundle_resources
982        if was_mac_bundle_resource or \
983            int(rule.get('process_outputs_as_mac_bundle_resources', False)):
984          for output in concrete_outputs_for_this_rule_source:
985            AddResourceToTarget(output, pbxp, xct)
986
987        # Do we have a message to print when this rule runs?
988        message = rule.get('message')
989        if message:
990          message = gyp.common.EncodePOSIXShellArgument(message)
991          message = ExpandXcodeVariables(message, rule_input_dict)
992        messages.append(message)
993
994        # Turn the list into a string that can be passed to a shell.
995        action_string = gyp.common.EncodePOSIXShellList(rule['action'])
996
997        action = ExpandXcodeVariables(action_string, rule_input_dict)
998        actions.append(action)
999
1000      if len(concrete_outputs_all) > 0:
1001        # TODO(mark): There's a possibility for collision here.  Consider
1002        # target "t" rule "A_r" and target "t_A" rule "r".
1003        makefile_name = '%s.make' % re.sub(
1004            '[^a-zA-Z0-9_]', '_' , '%s_%s' % (target_name, rule['rule_name']))
1005        makefile_path = os.path.join(xcode_projects[build_file].path,
1006                                     makefile_name)
1007        # TODO(mark): try/close?  Write to a temporary file and swap it only
1008        # if it's got changes?
1009        makefile = open(makefile_path, 'wb')
1010
1011        # make will build the first target in the makefile by default.  By
1012        # convention, it's called "all".  List all (or at least one)
1013        # concrete output for each rule source as a prerequisite of the "all"
1014        # target.
1015        makefile.write('all: \\\n')
1016        for concrete_output_index in \
1017            range(0, len(concrete_outputs_by_rule_source)):
1018          # Only list the first (index [0]) concrete output of each input
1019          # in the "all" target.  Otherwise, a parallel make (-j > 1) would
1020          # attempt to process each input multiple times simultaneously.
1021          # Otherwise, "all" could just contain the entire list of
1022          # concrete_outputs_all.
1023          concrete_output = \
1024              concrete_outputs_by_rule_source[concrete_output_index][0]
1025          if concrete_output_index == len(concrete_outputs_by_rule_source) - 1:
1026            eol = ''
1027          else:
1028            eol = ' \\'
1029          makefile.write('    %s%s\n' % (concrete_output, eol))
1030
1031        for (rule_source, concrete_outputs, message, action) in \
1032            zip(rule['rule_sources'], concrete_outputs_by_rule_source,
1033                messages, actions):
1034          makefile.write('\n')
1035
1036          # Add a rule that declares it can build each concrete output of a
1037          # rule source.  Collect the names of the directories that are
1038          # required.
1039          concrete_output_dirs = []
1040          for concrete_output_index in range(0, len(concrete_outputs)):
1041            concrete_output = concrete_outputs[concrete_output_index]
1042            if concrete_output_index == 0:
1043              bol = ''
1044            else:
1045              bol = '    '
1046            makefile.write('%s%s \\\n' % (bol, concrete_output))
1047
1048            concrete_output_dir = posixpath.dirname(concrete_output)
1049            if (concrete_output_dir and
1050                concrete_output_dir not in concrete_output_dirs):
1051              concrete_output_dirs.append(concrete_output_dir)
1052
1053          makefile.write('    : \\\n')
1054
1055          # The prerequisites for this rule are the rule source itself and
1056          # the set of additional rule inputs, if any.
1057          prerequisites = [rule_source]
1058          prerequisites.extend(rule.get('inputs', []))
1059          for prerequisite_index in range(0, len(prerequisites)):
1060            prerequisite = prerequisites[prerequisite_index]
1061            if prerequisite_index == len(prerequisites) - 1:
1062              eol = ''
1063            else:
1064              eol = ' \\'
1065            makefile.write('    %s%s\n' % (prerequisite, eol))
1066
1067          # Make sure that output directories exist before executing the rule
1068          # action.
1069          if len(concrete_output_dirs) > 0:
1070            makefile.write('\t@mkdir -p "%s"\n' %
1071                           '" "'.join(concrete_output_dirs))
1072
1073          # The rule message and action have already had the necessary variable
1074          # substitutions performed.
1075          if message:
1076            # Mark it with note: so Xcode picks it up in build output.
1077            makefile.write('\t@echo note: %s\n' % message)
1078          makefile.write('\t%s\n' % action)
1079
1080        makefile.close()
1081
1082        # It might be nice to ensure that needed output directories exist
1083        # here rather than in each target in the Makefile, but that wouldn't
1084        # work if there ever was a concrete output that had an input-dependent
1085        # variable anywhere other than in the leaf position.
1086
1087        # Don't declare any inputPaths or outputPaths.  If they're present,
1088        # Xcode will provide a slight optimization by only running the script
1089        # phase if any output is missing or outdated relative to any input.
1090        # Unfortunately, it will also assume that all outputs are touched by
1091        # the script, and if the outputs serve as files in a compilation
1092        # phase, they will be unconditionally rebuilt.  Since make might not
1093        # rebuild everything that could be declared here as an output, this
1094        # extra compilation activity is unnecessary.  With inputPaths and
1095        # outputPaths not supplied, make will always be called, but it knows
1096        # enough to not do anything when everything is up-to-date.
1097
1098        # To help speed things up, pass -j COUNT to make so it does some work
1099        # in parallel.  Don't use ncpus because Xcode will build ncpus targets
1100        # in parallel and if each target happens to have a rules step, there
1101        # would be ncpus^2 things going.  With a machine that has 2 quad-core
1102        # Xeons, a build can quickly run out of processes based on
1103        # scheduling/other tasks, and randomly failing builds are no good.
1104        script = \
1105"""JOB_COUNT="$(/usr/sbin/sysctl -n hw.ncpu)"
1106if [ "${JOB_COUNT}" -gt 4 ]; then
1107  JOB_COUNT=4
1108fi
1109exec xcrun make -f "${PROJECT_FILE_PATH}/%s" -j "${JOB_COUNT}"
1110exit 1
1111""" % makefile_name
1112        ssbp = gyp.xcodeproj_file.PBXShellScriptBuildPhase({
1113              'name': 'Rule "' + rule['rule_name'] + '"',
1114              'shellScript': script,
1115              'showEnvVarsInLog': 0,
1116            })
1117
1118        if support_xct:
1119          support_xct.AppendProperty('buildPhases', ssbp)
1120        else:
1121          # TODO(mark): this assumes too much knowledge of the internals of
1122          # xcodeproj_file; some of these smarts should move into xcodeproj_file
1123          # itself.
1124          xct._properties['buildPhases'].insert(prebuild_index, ssbp)
1125          prebuild_index = prebuild_index + 1
1126
1127      # Extra rule inputs also go into the project file.  Concrete outputs were
1128      # already added when they were computed.
1129      groups = ['inputs', 'inputs_excluded']
1130      if skip_excluded_files:
1131        groups = [x for x in groups if not x.endswith('_excluded')]
1132      for group in groups:
1133        for item in rule.get(group, []):
1134          pbxp.AddOrGetFileInRootGroup(item)
1135
1136    # Add "sources".
1137    for source in spec.get('sources', []):
1138      (source_root, source_extension) = posixpath.splitext(source)
1139      if source_extension[1:] not in rules_by_ext:
1140        # AddSourceToTarget will add the file to a root group if it's not
1141        # already there.
1142        AddSourceToTarget(source, type, pbxp, xct)
1143      else:
1144        pbxp.AddOrGetFileInRootGroup(source)
1145
1146    # Add "mac_bundle_resources" and "mac_framework_private_headers" if
1147    # it's a bundle of any type.
1148    if is_bundle:
1149      for resource in tgt_mac_bundle_resources:
1150        (resource_root, resource_extension) = posixpath.splitext(resource)
1151        if resource_extension[1:] not in rules_by_ext:
1152          AddResourceToTarget(resource, pbxp, xct)
1153        else:
1154          pbxp.AddOrGetFileInRootGroup(resource)
1155
1156      for header in spec.get('mac_framework_private_headers', []):
1157        AddHeaderToTarget(header, pbxp, xct, False)
1158
1159    # Add "mac_framework_headers". These can be valid for both frameworks
1160    # and static libraries.
1161    if is_bundle or type == 'static_library':
1162      for header in spec.get('mac_framework_headers', []):
1163        AddHeaderToTarget(header, pbxp, xct, True)
1164
1165    # Add "copies".
1166    pbxcp_dict = {}
1167    for copy_group in spec.get('copies', []):
1168      dest = copy_group['destination']
1169      if dest[0] not in ('/', '$'):
1170        # Relative paths are relative to $(SRCROOT).
1171        dest = '$(SRCROOT)/' + dest
1172
1173      code_sign = int(copy_group.get('xcode_code_sign', 0))
1174      settings = (None, '{ATTRIBUTES = (CodeSignOnCopy, ); }')[code_sign]
1175
1176      # Coalesce multiple "copies" sections in the same target with the same
1177      # "destination" property into the same PBXCopyFilesBuildPhase, otherwise
1178      # they'll wind up with ID collisions.
1179      pbxcp = pbxcp_dict.get(dest, None)
1180      if pbxcp is None:
1181        pbxcp = gyp.xcodeproj_file.PBXCopyFilesBuildPhase({
1182              'name': 'Copy to ' + copy_group['destination']
1183            },
1184            parent=xct)
1185        pbxcp.SetDestination(dest)
1186
1187        # TODO(mark): The usual comment about this knowing too much about
1188        # gyp.xcodeproj_file internals applies.
1189        xct._properties['buildPhases'].insert(prebuild_index, pbxcp)
1190
1191        pbxcp_dict[dest] = pbxcp
1192
1193      for file in copy_group['files']:
1194        pbxcp.AddFile(file, settings)
1195
1196    # Excluded files can also go into the project file.
1197    if not skip_excluded_files:
1198      for key in ['sources', 'mac_bundle_resources', 'mac_framework_headers',
1199                  'mac_framework_private_headers']:
1200        excluded_key = key + '_excluded'
1201        for item in spec.get(excluded_key, []):
1202          pbxp.AddOrGetFileInRootGroup(item)
1203
1204    # So can "inputs" and "outputs" sections of "actions" groups.
1205    groups = ['inputs', 'inputs_excluded', 'outputs', 'outputs_excluded']
1206    if skip_excluded_files:
1207      groups = [x for x in groups if not x.endswith('_excluded')]
1208    for action in spec.get('actions', []):
1209      for group in groups:
1210        for item in action.get(group, []):
1211          # Exclude anything in BUILT_PRODUCTS_DIR.  They're products, not
1212          # sources.
1213          if not item.startswith('$(BUILT_PRODUCTS_DIR)/'):
1214            pbxp.AddOrGetFileInRootGroup(item)
1215
1216    for postbuild in spec.get('postbuilds', []):
1217      action_string_sh = gyp.common.EncodePOSIXShellList(postbuild['action'])
1218      script = 'exec ' + action_string_sh + '\nexit 1\n'
1219
1220      # Make the postbuild step depend on the output of ld or ar from this
1221      # target. Apparently putting the script step after the link step isn't
1222      # sufficient to ensure proper ordering in all cases. With an input
1223      # declared but no outputs, the script step should run every time, as
1224      # desired.
1225      ssbp = gyp.xcodeproj_file.PBXShellScriptBuildPhase({
1226            'inputPaths': ['$(BUILT_PRODUCTS_DIR)/$(EXECUTABLE_PATH)'],
1227            'name': 'Postbuild "' + postbuild['postbuild_name'] + '"',
1228            'shellScript': script,
1229            'showEnvVarsInLog': 0,
1230          })
1231      xct.AppendProperty('buildPhases', ssbp)
1232
1233    # Add dependencies before libraries, because adding a dependency may imply
1234    # adding a library.  It's preferable to keep dependencies listed first
1235    # during a link phase so that they can override symbols that would
1236    # otherwise be provided by libraries, which will usually include system
1237    # libraries.  On some systems, ld is finicky and even requires the
1238    # libraries to be ordered in such a way that unresolved symbols in
1239    # earlier-listed libraries may only be resolved by later-listed libraries.
1240    # The Mac linker doesn't work that way, but other platforms do, and so
1241    # their linker invocations need to be constructed in this way.  There's
1242    # no compelling reason for Xcode's linker invocations to differ.
1243
1244    if 'dependencies' in spec:
1245      for dependency in spec['dependencies']:
1246        xct.AddDependency(xcode_targets[dependency])
1247        # The support project also gets the dependencies (in case they are
1248        # needed for the actions/rules to work).
1249        if support_xct:
1250          support_xct.AddDependency(xcode_targets[dependency])
1251
1252    if 'libraries' in spec:
1253      for library in spec['libraries']:
1254        xct.FrameworksPhase().AddFile(library)
1255        # Add the library's directory to LIBRARY_SEARCH_PATHS if necessary.
1256        # I wish Xcode handled this automatically.
1257        library_dir = posixpath.dirname(library)
1258        if library_dir not in xcode_standard_library_dirs and (
1259            not xct.HasBuildSetting(_library_search_paths_var) or
1260            library_dir not in xct.GetBuildSetting(_library_search_paths_var)):
1261          xct.AppendBuildSetting(_library_search_paths_var, library_dir)
1262
1263    for configuration_name in configuration_names:
1264      configuration = spec['configurations'][configuration_name]
1265      xcbc = xct.ConfigurationNamed(configuration_name)
1266      for include_dir in configuration.get('mac_framework_dirs', []):
1267        xcbc.AppendBuildSetting('FRAMEWORK_SEARCH_PATHS', include_dir)
1268      for include_dir in configuration.get('include_dirs', []):
1269        xcbc.AppendBuildSetting('HEADER_SEARCH_PATHS', include_dir)
1270      for library_dir in configuration.get('library_dirs', []):
1271        if library_dir not in xcode_standard_library_dirs and (
1272            not xcbc.HasBuildSetting(_library_search_paths_var) or
1273            library_dir not in xcbc.GetBuildSetting(_library_search_paths_var)):
1274          xcbc.AppendBuildSetting(_library_search_paths_var, library_dir)
1275
1276      if 'defines' in configuration:
1277        for define in configuration['defines']:
1278          set_define = EscapeXcodeDefine(define)
1279          xcbc.AppendBuildSetting('GCC_PREPROCESSOR_DEFINITIONS', set_define)
1280      if 'xcode_settings' in configuration:
1281        for xck, xcv in configuration['xcode_settings'].items():
1282          xcbc.SetBuildSetting(xck, xcv)
1283      if 'xcode_config_file' in configuration:
1284        config_ref = pbxp.AddOrGetFileInRootGroup(
1285            configuration['xcode_config_file'])
1286        xcbc.SetBaseConfiguration(config_ref)
1287
1288  build_files = []
1289  for build_file, build_file_dict in data.items():
1290    if build_file.endswith('.gyp'):
1291      build_files.append(build_file)
1292
1293  for build_file in build_files:
1294    xcode_projects[build_file].Finalize1(xcode_targets, serialize_all_tests)
1295
1296  for build_file in build_files:
1297    xcode_projects[build_file].Finalize2(xcode_targets,
1298                                         xcode_target_to_target_dict)
1299
1300  for build_file in build_files:
1301    xcode_projects[build_file].Write()
1302