Team Ai
Datasetpublic

codekingpro/portable-devtools

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

Showing the first 1,200 of 1390 lines. Download the file for the rest.

codekingpro/portable-devtools · Team Ai