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