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