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