1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 #pragma once
27 
28 #include "jucer_XcodeProjectParser.h"
29 
30 //==============================================================================
31 namespace
32 {
33     static const char* const iOSDefaultVersion = "9.3";
34     static const StringArray iOSVersions { "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3",
35                                            "11.0", "12.0", "13.0", "14.0" };
36 
37     enum class MacOSVersion
38     {
39         v10_7,
40         v10_8,
41         v10_9,
42         v10_10,
43         v10_11,
44         v10_12,
45         v10_13,
46         v10_14,
47         v10_15,
48         v10_16,
49         v11_0,
50         v11_1,
51     };
52 
getName(MacOSVersion m)53     static const char* getName (MacOSVersion m)
54     {
55         switch (m)
56         {
57             case MacOSVersion::v10_7:   return "10.7";
58             case MacOSVersion::v10_8:   return "10.8";
59             case MacOSVersion::v10_9:   return "10.9";
60             case MacOSVersion::v10_10:  return "10.10";
61             case MacOSVersion::v10_11:  return "10.11";
62             case MacOSVersion::v10_12:  return "10.12";
63             case MacOSVersion::v10_13:  return "10.13";
64             case MacOSVersion::v10_14:  return "10.14";
65             case MacOSVersion::v10_15:  return "10.15";
66             case MacOSVersion::v10_16:  return "10.16";
67             case MacOSVersion::v11_0:   return "11.0";
68             case MacOSVersion::v11_1:   return "11.1";
69             default:                    break;
70         }
71 
72         jassertfalse;
73         return "";
74     }
75 
getDisplayName(MacOSVersion m)76     static String getDisplayName (MacOSVersion m) { return getName (m) + String (" SDK"); }
getRootName(MacOSVersion m)77     static String getRootName    (MacOSVersion m) { return String ("macosx") + getName (m); }
78 
79     constexpr auto nextMacOSVersion       = (MacOSVersion) ((int) MacOSVersion::v11_1 + 1);
80     constexpr auto oldestDeploymentTarget = MacOSVersion::v10_7;
81     constexpr auto macOSDefaultVersion    = MacOSVersion::v10_11;
82     constexpr auto oldestSDKVersion       = MacOSVersion::v10_11;
83     constexpr auto minimumAUv3SDKVersion  = MacOSVersion::v10_11;
84 
85     static MacOSVersion& operator++ (MacOSVersion& m)
86     {
87         return m = (MacOSVersion) ((int) m + 1);
88     }
89 
getOSXSDKVersion(const String & sdkVersion)90     static String getOSXSDKVersion (const String& sdkVersion)
91     {
92         for (auto v = oldestSDKVersion; v != nextMacOSVersion; ++v)
93             if (sdkVersion == getDisplayName (v))
94                 return getRootName (v);
95 
96         return "macosx";
97     }
98 
99     template <class ContainerType>
getSDKChoiceList(MacOSVersion oldestVersion,bool displayName)100     static ContainerType getSDKChoiceList (MacOSVersion oldestVersion, bool displayName)
101     {
102         ContainerType container;
103 
104         for (auto v = oldestVersion; v != nextMacOSVersion; ++v)
105             container.add (displayName ? getDisplayName (v) : getName (v));
106 
107         return container;
108     }
109 
110     static const char* const osxArch_Default        = "default";
111     static const char* const osxArch_Native         = "Native";
112     static const char* const osxArch_32BitUniversal = "32BitUniversal";
113     static const char* const osxArch_64BitUniversal = "64BitUniversal";
114     static const char* const osxArch_64Bit          = "64BitIntel";
115 }
116 
117 //==============================================================================
118 class XcodeProjectExporter  : public ProjectExporter
119 {
120 public:
121     //==============================================================================
getDisplayNameMac()122     static String getDisplayNameMac()        { return "Xcode (macOS)"; }
getDisplayNameiOS()123     static String getDisplayNameiOS()        { return "Xcode (iOS)"; }
124 
getTargetFolderNameMac()125     static String getTargetFolderNameMac()   { return "MacOSX"; }
getTargetFolderNameiOS()126     static String getTargetFolderNameiOS()   { return "iOS"; }
127 
getValueTreeTypeNameMac()128     static String getValueTreeTypeNameMac()  { return "XCODE_MAC"; }
getValueTreeTypeNameiOS()129     static String getValueTreeTypeNameiOS()  { return "XCODE_IPHONE"; }
130 
131     //==============================================================================
XcodeProjectExporter(Project & p,const ValueTree & t,const bool isIOS)132     XcodeProjectExporter (Project& p, const ValueTree& t, const bool isIOS)
133         : ProjectExporter (p, t),
134           xcodeCanUseDwarf (true),
135           iOS (isIOS),
136           customPListValue                             (settings, Ids::customPList,                             getUndoManager()),
137           pListPrefixHeaderValue                       (settings, Ids::pListPrefixHeader,                       getUndoManager()),
138           pListPreprocessValue                         (settings, Ids::pListPreprocess,                         getUndoManager()),
139           subprojectsValue                             (settings, Ids::xcodeSubprojects,                        getUndoManager()),
140           validArchsValue                              (settings, Ids::xcodeValidArchs,                         getUndoManager(), getAllArchs(), ","),
141           extraFrameworksValue                         (settings, Ids::extraFrameworks,                         getUndoManager()),
142           frameworkSearchPathsValue                    (settings, Ids::frameworkSearchPaths,                    getUndoManager()),
143           extraCustomFrameworksValue                   (settings, Ids::extraCustomFrameworks,                   getUndoManager()),
144           embeddedFrameworksValue                      (settings, Ids::embeddedFrameworks,                      getUndoManager()),
145           postbuildCommandValue                        (settings, Ids::postbuildCommand,                        getUndoManager()),
146           prebuildCommandValue                         (settings, Ids::prebuildCommand,                         getUndoManager()),
147           duplicateAppExResourcesFolderValue           (settings, Ids::duplicateAppExResourcesFolder,           getUndoManager(), true),
148           iosDeviceFamilyValue                         (settings, Ids::iosDeviceFamily,                         getUndoManager(), "1,2"),
149           iPhoneScreenOrientationValue                 (settings, Ids::iPhoneScreenOrientation,                 getUndoManager(), getDefaultScreenOrientations(), ","),
150           iPadScreenOrientationValue                   (settings, Ids::iPadScreenOrientation,                   getUndoManager(), getDefaultScreenOrientations(), ","),
151           customXcodeResourceFoldersValue              (settings, Ids::customXcodeResourceFolders,              getUndoManager()),
152           customXcassetsFolderValue                    (settings, Ids::customXcassetsFolder,                    getUndoManager()),
153           appSandboxValue                              (settings, Ids::appSandbox,                              getUndoManager()),
154           appSandboxInheritanceValue                   (settings, Ids::appSandboxInheritance,                   getUndoManager()),
155           appSandboxOptionsValue                       (settings, Ids::appSandboxOptions,                       getUndoManager(), Array<var>(), ","),
156           hardenedRuntimeValue                         (settings, Ids::hardenedRuntime,                         getUndoManager()),
157           hardenedRuntimeOptionsValue                  (settings, Ids::hardenedRuntimeOptions,                  getUndoManager(), Array<var>(), ","),
158           microphonePermissionNeededValue              (settings, Ids::microphonePermissionNeeded,              getUndoManager()),
159           microphonePermissionsTextValue               (settings, Ids::microphonePermissionsText,               getUndoManager(),
160                                                         "This app requires audio input. If you do not have an audio interface connected it will use the built-in microphone."),
161           cameraPermissionNeededValue                  (settings, Ids::cameraPermissionNeeded,                  getUndoManager()),
162           cameraPermissionTextValue                    (settings, Ids::cameraPermissionText,                    getUndoManager(),
163                                                         "This app requires access to the camera to function correctly."),
164           bluetoothPermissionNeededValue               (settings, Ids::iosBluetoothPermissionNeeded,            getUndoManager()),
165           bluetoothPermissionTextValue                 (settings, Ids::iosBluetoothPermissionText,              getUndoManager(),
166                                                         "This app requires access to Bluetooth to function correctly."),
167           sendAppleEventsPermissionNeededValue         (settings, Ids::sendAppleEventsPermissionNeeded, getUndoManager()),
168           sendAppleEventsPermissionTextValue           (settings, Ids::sendAppleEventsPermissionText, getUndoManager(),
169                                                         "This app requires the ability to send Apple events to function correctly."),
170           uiFileSharingEnabledValue                    (settings, Ids::UIFileSharingEnabled,                    getUndoManager()),
171           uiSupportsDocumentBrowserValue               (settings, Ids::UISupportsDocumentBrowser,               getUndoManager()),
172           uiStatusBarHiddenValue                       (settings, Ids::UIStatusBarHidden,                       getUndoManager()),
173           uiRequiresFullScreenValue                    (settings, Ids::UIRequiresFullScreen,                    getUndoManager(), true),
174           documentExtensionsValue                      (settings, Ids::documentExtensions,                      getUndoManager()),
175           iosInAppPurchasesValue                       (settings, Ids::iosInAppPurchases,                       getUndoManager()),
176           iosContentSharingValue                       (settings, Ids::iosContentSharing,                       getUndoManager(), true),
177           iosBackgroundAudioValue                      (settings, Ids::iosBackgroundAudio,                      getUndoManager()),
178           iosBackgroundBleValue                        (settings, Ids::iosBackgroundBle,                        getUndoManager()),
179           iosPushNotificationsValue                    (settings, Ids::iosPushNotifications,                    getUndoManager()),
180           iosAppGroupsValue                            (settings, Ids::iosAppGroups,                            getUndoManager()),
181           iCloudPermissionsValue                       (settings, Ids::iCloudPermissions,                       getUndoManager()),
182           iosDevelopmentTeamIDValue                    (settings, Ids::iosDevelopmentTeamID,                    getUndoManager()),
183           iosAppGroupsIDValue                          (settings, Ids::iosAppGroupsId,                          getUndoManager()),
184           keepCustomXcodeSchemesValue                  (settings, Ids::keepCustomXcodeSchemes,                  getUndoManager()),
185           useHeaderMapValue                            (settings, Ids::useHeaderMap,                            getUndoManager()),
186           customLaunchStoryboardValue                  (settings, Ids::customLaunchStoryboard,                  getUndoManager()),
187           exporterBundleIdentifierValue                (settings, Ids::bundleIdentifier,                        getUndoManager()),
188           suppressPlistResourceUsageValue              (settings, Ids::suppressPlistResourceUsage,              getUndoManager()),
189           useLegacyBuildSystemValue                    (settings, Ids::useLegacyBuildSystem,                    getUndoManager())
190     {
191         if (iOS)
192         {
193             name = getDisplayNameiOS();
194             targetLocationValue.setDefault (getDefaultBuildsRootFolder() + getTargetFolderNameiOS());
195         }
196         else
197         {
198             name = getDisplayNameMac();
199             targetLocationValue.setDefault (getDefaultBuildsRootFolder() + getTargetFolderNameMac());
200         }
201     }
202 
createForSettings(Project & projectToUse,const ValueTree & settingsToUse)203     static XcodeProjectExporter* createForSettings (Project& projectToUse, const ValueTree& settingsToUse)
204     {
205         if (settingsToUse.hasType (getValueTreeTypeNameMac()))  return new XcodeProjectExporter (projectToUse, settingsToUse, false);
206         if (settingsToUse.hasType (getValueTreeTypeNameiOS()))  return new XcodeProjectExporter (projectToUse, settingsToUse, true);
207 
208         return nullptr;
209     }
210 
211     //==============================================================================
getPListToMergeString()212     String getPListToMergeString() const                    { return customPListValue.get(); }
getPListPrefixHeaderString()213     String getPListPrefixHeaderString() const               { return pListPrefixHeaderValue.get(); }
isPListPreprocessEnabled()214     bool isPListPreprocessEnabled() const                   { return pListPreprocessValue.get(); }
215 
getSubprojectsString()216     String getSubprojectsString() const                     { return subprojectsValue.get(); }
217 
getExtraFrameworksString()218     String getExtraFrameworksString() const                 { return extraFrameworksValue.get(); }
getFrameworkSearchPathsString()219     String getFrameworkSearchPathsString() const            { return frameworkSearchPathsValue.get(); }
getExtraCustomFrameworksString()220     String getExtraCustomFrameworksString() const           { return extraCustomFrameworksValue.get(); }
getEmbeddedFrameworksString()221     String getEmbeddedFrameworksString() const              { return embeddedFrameworksValue.get(); }
222 
getPostBuildScript()223     String getPostBuildScript() const                       { return postbuildCommandValue.get(); }
getPreBuildScript()224     String getPreBuildScript() const                        { return prebuildCommandValue.get(); }
225 
shouldDuplicateAppExResourcesFolder()226     bool shouldDuplicateAppExResourcesFolder() const        { return duplicateAppExResourcesFolderValue.get(); }
227 
getDeviceFamilyString()228     String getDeviceFamilyString() const                    { return iosDeviceFamilyValue.get(); }
229 
getDefaultScreenOrientations()230     Array<var> getDefaultScreenOrientations() const         { return { "UIInterfaceOrientationPortrait",
231                                                                        "UIInterfaceOrientationLandscapeLeft",
232                                                                        "UIInterfaceOrientationLandscapeRight" }; }
233 
getAllArchs()234     Array<var> getAllArchs() const                          { return { "i386", "x86_64", "arm64", "arm64e"}; }
235 
getiPhoneScreenOrientations()236     Array<var> getiPhoneScreenOrientations() const          { return *iPhoneScreenOrientationValue.get().getArray(); }
getiPadScreenOrientations()237     Array<var> getiPadScreenOrientations() const            { return *iPadScreenOrientationValue.get().getArray(); }
238 
getCustomResourceFoldersString()239     String getCustomResourceFoldersString() const           { return customXcodeResourceFoldersValue.get().toString().replaceCharacters ("\r\n", "::"); }
getCustomXcassetsFolderString()240     String getCustomXcassetsFolderString() const            { return customXcassetsFolderValue.get(); }
getCustomLaunchStoryboardString()241     String getCustomLaunchStoryboardString() const          { return customLaunchStoryboardValue.get(); }
shouldAddStoryboardToProject()242     bool shouldAddStoryboardToProject() const               { return getCustomLaunchStoryboardString().isNotEmpty() || getCustomXcassetsFolderString().isEmpty(); }
243 
isHardenedRuntimeEnabled()244     bool isHardenedRuntimeEnabled() const                   { return hardenedRuntimeValue.get(); }
getHardenedRuntimeOptions()245     Array<var> getHardenedRuntimeOptions() const            { return *hardenedRuntimeOptionsValue.get().getArray(); }
246 
isAppSandboxEnabled()247     bool isAppSandboxEnabled() const                        { return appSandboxValue.get(); }
isAppSandboxInhertianceEnabled()248     bool isAppSandboxInhertianceEnabled() const             { return appSandboxInheritanceValue.get(); }
getAppSandboxOptions()249     Array<var> getAppSandboxOptions() const                 { return *appSandboxOptionsValue.get().getArray(); }
250 
getValidArchs()251     Array<var> getValidArchs() const                        { return *validArchsValue.get().getArray(); }
252 
isMicrophonePermissionEnabled()253     bool isMicrophonePermissionEnabled() const              { return microphonePermissionNeededValue.get(); }
getMicrophonePermissionsTextString()254     String getMicrophonePermissionsTextString() const       { return microphonePermissionsTextValue.get(); }
255 
isCameraPermissionEnabled()256     bool isCameraPermissionEnabled() const                  { return cameraPermissionNeededValue.get(); }
getCameraPermissionTextString()257     String getCameraPermissionTextString() const            { return cameraPermissionTextValue.get(); }
258 
isBluetoothPermissionEnabled()259     bool isBluetoothPermissionEnabled() const               { return bluetoothPermissionNeededValue.get(); }
getBluetoothPermissionTextString()260     String getBluetoothPermissionTextString() const         { return bluetoothPermissionTextValue.get(); }
261 
isSendAppleEventsPermissionEnabled()262     bool isSendAppleEventsPermissionEnabled() const         { return sendAppleEventsPermissionNeededValue.get(); }
getSendAppleEventsPermissionTextString()263     String getSendAppleEventsPermissionTextString() const   { return sendAppleEventsPermissionTextValue.get(); }
264 
isInAppPurchasesEnabled()265     bool isInAppPurchasesEnabled() const                    { return iosInAppPurchasesValue.get(); }
isContentSharingEnabled()266     bool isContentSharingEnabled() const                    { return iosContentSharingValue.get(); }
isBackgroundAudioEnabled()267     bool isBackgroundAudioEnabled() const                   { return iosBackgroundAudioValue.get(); }
isBackgroundBleEnabled()268     bool isBackgroundBleEnabled() const                     { return iosBackgroundBleValue.get(); }
isPushNotificationsEnabled()269     bool isPushNotificationsEnabled() const                 { return iosPushNotificationsValue.get(); }
isAppGroupsEnabled()270     bool isAppGroupsEnabled() const                         { return iosAppGroupsValue.get(); }
isiCloudPermissionsEnabled()271     bool isiCloudPermissionsEnabled() const                 { return iCloudPermissionsValue.get(); }
isFileSharingEnabled()272     bool isFileSharingEnabled() const                       { return uiFileSharingEnabledValue.get(); }
isDocumentBrowserEnabled()273     bool isDocumentBrowserEnabled() const                   { return uiSupportsDocumentBrowserValue.get(); }
isStatusBarHidden()274     bool isStatusBarHidden() const                          { return uiStatusBarHiddenValue.get(); }
requiresFullScreen()275     bool requiresFullScreen() const                         { return uiRequiresFullScreenValue.get(); }
276 
getSuppressPlistResourceUsage()277     bool getSuppressPlistResourceUsage() const              { return suppressPlistResourceUsageValue.get(); }
278 
shouldUseLegacyBuildSystem()279     bool shouldUseLegacyBuildSystem() const                 { return useLegacyBuildSystemValue.get(); }
280 
getDocumentExtensionsString()281     String getDocumentExtensionsString() const              { return documentExtensionsValue.get(); }
282 
shouldKeepCustomXcodeSchemes()283     bool shouldKeepCustomXcodeSchemes() const               { return keepCustomXcodeSchemesValue.get(); }
284 
getDevelopmentTeamIDString()285     String getDevelopmentTeamIDString() const               { return iosDevelopmentTeamIDValue.get(); }
getAppGroupIdString()286     String getAppGroupIdString() const                      { return iosAppGroupsIDValue.get(); }
287 
getDefaultLaunchStoryboardName()288     String getDefaultLaunchStoryboardName() const           { return "LaunchScreen"; }
289 
290     //==============================================================================
usesMMFiles()291     bool usesMMFiles() const override                       { return true; }
canCopeWithDuplicateFiles()292     bool canCopeWithDuplicateFiles() override               { return true; }
supportsUserDefinedConfigurations()293     bool supportsUserDefinedConfigurations() const override { return true; }
294 
isXcode()295     bool isXcode() const override                           { return true; }
isVisualStudio()296     bool isVisualStudio() const override                    { return false; }
isCodeBlocks()297     bool isCodeBlocks() const override                      { return false; }
isMakefile()298     bool isMakefile() const override                        { return false; }
isAndroidStudio()299     bool isAndroidStudio() const override                   { return false; }
isCLion()300     bool isCLion() const override                           { return false; }
301 
isAndroid()302     bool isAndroid() const override                         { return false; }
isWindows()303     bool isWindows() const override                         { return false; }
isLinux()304     bool isLinux() const override                           { return false; }
isOSX()305     bool isOSX() const override                             { return ! iOS; }
isiOS()306     bool isiOS() const override                             { return iOS; }
307 
supportsPrecompiledHeaders()308     bool supportsPrecompiledHeaders() const override        { return true; }
309 
getNewLineString()310     String getNewLineString() const override                { return "\n"; }
311 
supportsTargetType(build_tools::ProjectType::Target::Type type)312     bool supportsTargetType (build_tools::ProjectType::Target::Type type) const override
313     {
314         switch (type)
315         {
316             case build_tools::ProjectType::Target::AudioUnitv3PlugIn:
317             case build_tools::ProjectType::Target::StandalonePlugIn:
318             case build_tools::ProjectType::Target::GUIApp:
319             case build_tools::ProjectType::Target::StaticLibrary:
320             case build_tools::ProjectType::Target::DynamicLibrary:
321             case build_tools::ProjectType::Target::SharedCodeTarget:
322             case build_tools::ProjectType::Target::AggregateTarget:
323                 return true;
324             case build_tools::ProjectType::Target::ConsoleApp:
325             case build_tools::ProjectType::Target::VSTPlugIn:
326             case build_tools::ProjectType::Target::VST3PlugIn:
327             case build_tools::ProjectType::Target::AAXPlugIn:
328             case build_tools::ProjectType::Target::RTASPlugIn:
329             case build_tools::ProjectType::Target::AudioUnitPlugIn:
330             case build_tools::ProjectType::Target::UnityPlugIn:
331                 return ! iOS;
332             case build_tools::ProjectType::Target::unspecified:
333             default:
334                 break;
335         }
336 
337         return false;
338     }
339 
createExporterProperties(PropertyListBuilder & props)340     void createExporterProperties (PropertyListBuilder& props) override
341     {
342         if (iOS)
343         {
344             props.add (new TextPropertyComponent (customXcassetsFolderValue, "Custom Xcassets Folder", 128, false),
345                        "If this field is not empty, your Xcode project will use the custom xcassets folder specified here "
346                        "for the app icons and launchimages, and will ignore the Icon files specified above. This will also prevent "
347                        "a launch storyboard from being used.");
348 
349             props.add (new TextPropertyComponent (customLaunchStoryboardValue, "Custom Launch Storyboard", 256, false),
350                        "If this field is not empty then the specified launch storyboard file will be added to the project as an Xcode "
351                        "resource and will be used for the app's launch screen, otherwise a default blank launch storyboard will be used. "
352                        "The file path should be relative to the project folder.");
353         }
354 
355         props.add (new TextPropertyComponent (customXcodeResourceFoldersValue, "Custom Xcode Resource Folders", 8192, true),
356                    "You can specify a list of custom resource folders here (separated by newlines or whitespace). "
357                    "References to these folders will then be added to the Xcode resources. "
358                    "This way you can specify them for OS X and iOS separately, and modify the content of the resource folders "
359                    "without re-saving the Projucer project.");
360 
361         if (getProject().isAudioPluginProject())
362             props.add (new ChoicePropertyComponent (duplicateAppExResourcesFolderValue, "Add Duplicate Resources Folder to App Extension"),
363                        "Disable this to prevent the Projucer from creating a duplicate resources folder for AUv3 app extensions.");
364 
365         if (iOS)
366         {
367             props.add (new ChoicePropertyComponent (iosDeviceFamilyValue, "Device Family",
368                                                     { "iPhone", "iPad", "Universal" },
369                                                     { "1",      "2",    "1,2" }),
370                        "The device family to target.");
371 
372             {
373                 StringArray orientationStrings { "Portrait", "Portrait Upside Down",
374                                                  "Landscape Left", "Landscape Right" };
375 
376                 Array<var> orientationVars { "UIInterfaceOrientationPortrait", "UIInterfaceOrientationPortraitUpsideDown",
377                                              "UIInterfaceOrientationLandscapeLeft", "UIInterfaceOrientationLandscapeRight" };
378 
379                 props.add (new MultiChoicePropertyComponent (iPhoneScreenOrientationValue, "iPhone Screen Orientation", orientationStrings, orientationVars),
380                            "The screen orientations that this app should support on iPhones.");
381 
382                 props.add (new MultiChoicePropertyComponent (iPadScreenOrientationValue, "iPad Screen Orientation", orientationStrings, orientationVars),
383                            "The screen orientations that this app should support on iPads.");
384             }
385 
386             props.add (new ChoicePropertyComponent (uiFileSharingEnabledValue, "File Sharing Enabled"),
387                        "Enable this to expose your app's files to iTunes.");
388 
389             props.add (new ChoicePropertyComponent (uiSupportsDocumentBrowserValue, "Support Document Browser"),
390                        "Enable this to allow the user to access your app documents from a native file chooser.");
391 
392             props.add (new ChoicePropertyComponent (uiStatusBarHiddenValue, "Status Bar Hidden"),
393                        "Enable this to disable the status bar in your app.");
394 
395             props.add (new ChoicePropertyComponent (uiRequiresFullScreenValue, "Requires Full Screen"),
396                        "Disable this to enable non-fullscreen views such as Slide Over or Split View in your app. "
397                        "You will also need to enable all orientations.");
398         }
399         else if (projectType.isGUIApplication())
400         {
401             props.add (new TextPropertyComponent (documentExtensionsValue, "Document File Extensions", 128, false),
402                        "A comma-separated list of file extensions for documents that your app can open. "
403                        "Using a leading '.' is optional, and the extensions are not case-sensitive.");
404         }
405 
406         props.add (new ChoicePropertyComponent (useLegacyBuildSystemValue, "Use Legacy Build System"),
407                    "Enable this to use the deprecated \"Legacy Build System\" in Xcode 10 and above. "
408                    "This may fix build issues that were introduced with the new build system in Xcode 10 and subsequently fixed in Xcode 10.2, "
409                    "however the new build system is recommended for apps targeting Apple silicon.");
410 
411         if (isOSX())
412         {
413             props.add (new MultiChoicePropertyComponent (validArchsValue, "Valid Architectures", getAllArchs(), getAllArchs()),
414                        "The full set of architectures which this project may target. "
415                        "Each configuration will build for the intersection of this property, and the per-configuration macOS Architecture property");
416 
417             props.add (new ChoicePropertyComponent (appSandboxValue, "Use App Sandbox"),
418                        "Enable this to use the app sandbox.");
419 
420             props.add (new ChoicePropertyComponentWithEnablement (appSandboxInheritanceValue, appSandboxValue, "App Sandbox Inheritance"),
421                        "If app sandbox is enabled, this setting will configure a child process to inherit the sandbox of its parent. "
422                        "Note that if you enable this and have specified any other app sandbox entitlements below, the child process "
423                        "will fail to launch.");
424 
425             std::vector<std::pair<String, String>> sandboxOptions
426             {
427                 { "Network: Incoming Connections (Server)",         "network.server" },
428                 { "Network: Outgoing Connections (Client)",         "network.client" },
429 
430                 { "Hardware: Camera",                               "device.camera" },
431                 { "Hardware: Microphone",                           "device.microphone" },
432                 { "Hardware: USB",                                  "device.usb" },
433                 { "Hardware: Printing",                             "print" },
434                 { "Hardware: Bluetooth",                            "device.bluetooth" },
435 
436                 { "App Data: Contacts",                             "personal-information.addressbook" },
437                 { "App Data: Location",                             "personal-information.location" },
438                 { "App Data: Calendar",                             "personal-information.calendars" },
439 
440                 { "File Access: User Selected File (Read Only)",    "files.user-selected.read-only" },
441                 { "File Access: User Selected File (Read/Write)",   "files.user-selected.read-write" },
442                 { "File Access: Downloads Folder (Read Only)",      "files.downloads.read-only" },
443                 { "File Access: Downloads Folder (Read/Write)",     "files.downloads.read-write" },
444                 { "File Access: Pictures Folder (Read Only)",       "files.pictures.read-only" },
445                 { "File Access: Pictures Folder (Read/Write)",      "files.pictures.read-write" },
446                 { "File Access: Music Folder (Read Only)",          "assets.music.read-only" },
447                 { "File Access: Music Folder (Read/Write)",         "assets.music.read-write" },
448                 { "File Access: Movies Folder (Read Only)",         "assets.movies.read-only" },
449                 { "File Access: Movies Folder (Read/Write)",        "assets.movies.read-write" },
450 
451                 { "Temporary Exception: Audio Unit Hosting",                       "temporary-exception.audio-unit-host" },
452                 { "Temporary Exception: Global Mach Service",                      "temporary-exception.mach-lookup.global-name" },
453                 { "Temporary Exception: Global Mach Service Dynamic Registration", "temporary-exception.mach-register.global-name" },
454                 { "Temporary Exception: Home Directory File Access (Read Only)",   "temporary-exception.files.home-relative-path.read-only" },
455                 { "Temporary Exception: Home Directory File Access (Read/Write)",  "temporary-exception.files.home-relative-path.read-write" },
456                 { "Temporary Exception: Absolute Path File Access (Read Only)",    "temporary-exception.files.absolute-path.read-only" },
457                 { "Temporary Exception: Absolute Path File Access (Read/Write)",   "temporary-exception.files.absolute-path.read-write" },
458                 { "Temporary Exception: IOKit User Client Class",                  "temporary-exception.iokit-user-client-class" },
459                 { "Temporary Exception: Shared Preference Domain (Read Only)",     "temporary-exception.shared-preference.read-only" },
460                 { "Temporary Exception: Shared Preference Domain (Read/Write)",    "temporary-exception.shared-preference.read-write" }
461             };
462 
463             StringArray sandboxKeys;
464             Array<var> sanboxValues;
465 
466             for (auto& opt : sandboxOptions)
467             {
468                 sandboxKeys.add (opt.first);
469                 sanboxValues.add ("com.apple.security." + opt.second);
470             }
471 
472             props.add (new MultiChoicePropertyComponentWithEnablement (appSandboxOptionsValue,
473                                                                        appSandboxValue,
474                                                                        "App Sandbox Options",
475                                                                        sandboxKeys,
476                                                                        sanboxValues));
477 
478             props.add (new ChoicePropertyComponent (hardenedRuntimeValue, "Use Hardened Runtime"),
479                        "Enable this to use the hardened runtime required for app notarization.");
480 
481             std::vector<std::pair<String, String>> hardeningOptions
482             {
483                 { "Runtime Exceptions: Allow Execution of JIT-compiled Code", "cs.allow-jit" },
484                 { "Runtime Exceptions: Allow Unsigned Executable Memory",     "cs.allow-unsigned-executable-memory" },
485                 { "Runtime Exceptions: Allow DYLD Environment Variables",     "cs.allow-dyld-environment-variables" },
486                 { "Runtime Exceptions: Disable Library Validation",           "cs.disable-library-validation" },
487                 { "Runtime Exceptions: Disable Executable Memory Protection", "cs.disable-executable-page-protection" },
488                 { "Runtime Exceptions: Debugging Tool",                       "cs.debugger" },
489 
490                 { "Resource Access: Audio Input",                             "device.audio-input" },
491                 { "Resource Access: Camera",                                  "device.camera" },
492                 { "Resource Access: Location",                                "personal-information.location" },
493                 { "Resource Access: Address Book",                            "personal-information.addressbook" },
494                 { "Resource Access: Calendar",                                "personal-information.calendars" },
495                 { "Resource Access: Photos Library",                          "personal-information.photos-library" },
496                 { "Resource Access: Apple Events",                            "automation.apple-events" }
497             };
498 
499             StringArray hardeningKeys;
500             Array<var> hardeningValues;
501 
502             for (auto& opt : hardeningOptions)
503             {
504                 hardeningKeys.add (opt.first);
505                 hardeningValues.add ("com.apple.security." + opt.second);
506             }
507 
508             props.add (new MultiChoicePropertyComponentWithEnablement (hardenedRuntimeOptionsValue,
509                                                                        hardenedRuntimeValue,
510                                                                        "Hardened Runtime Options",
511                                                                        hardeningKeys,
512                                                                        hardeningValues));
513         }
514 
515         props.add (new ChoicePropertyComponent (microphonePermissionNeededValue, "Microphone Access"),
516                    "Enable this to allow your app to use the microphone. "
517                    "The user of your app will be prompted to grant microphone access permissions.");
518 
519         props.add (new TextPropertyComponentWithEnablement (microphonePermissionsTextValue, microphonePermissionNeededValue,
520                                                             "Microphone Access Text", 1024, false),
521                    "A short description of why your app requires microphone access.");
522 
523         props.add (new ChoicePropertyComponent (cameraPermissionNeededValue, "Camera Access"),
524                    "Enable this to allow your app to use the camera. "
525                    "The user of your app will be prompted to grant camera access permissions.");
526 
527         props.add (new TextPropertyComponentWithEnablement (cameraPermissionTextValue, cameraPermissionNeededValue,
528                                                             "Camera Access Text", 1024, false),
529                    "A short description of why your app requires camera access.");
530 
531         props.add (new ChoicePropertyComponent (bluetoothPermissionNeededValue, "Bluetooth Access"),
532                    "Enable this to allow your app to use Bluetooth on iOS 13.0 and above, and macOS 11.0 and above. "
533                    "The user of your app will be prompted to grant Bluetooth access permissions.");
534 
535         props.add (new TextPropertyComponentWithEnablement (bluetoothPermissionTextValue, bluetoothPermissionNeededValue,
536                                                             "Bluetooth Access Text", 1024, false),
537                    "A short description of why your app requires Bluetooth access.");
538 
539         if (! iOS)
540         {
541             props.add (new ChoicePropertyComponent (sendAppleEventsPermissionNeededValue, "Send Apple Events"),
542                        "Enable this to allow your app to send Apple events. "
543                        "The user of your app will be prompted to grant permissions to control other apps.");
544 
545             props.add (new TextPropertyComponentWithEnablement (sendAppleEventsPermissionTextValue, sendAppleEventsPermissionNeededValue,
546                                                                 "Send Apple Events Text", 1024, false),
547                        "A short description of why your app requires the ability to send Apple events.");
548         }
549 
550         props.add (new ChoicePropertyComponent (iosInAppPurchasesValue, "In-App Purchases Capability"),
551                    "Enable this to grant your app the capability for in-app purchases. "
552                    "This option requires that you specify a valid Development Team ID.");
553 
554         if (iOS)
555         {
556             props.add (new ChoicePropertyComponent (iosContentSharingValue, "Content Sharing"),
557                        "Enable this to allow your app to share content with other apps.");
558 
559             props.add (new ChoicePropertyComponent (iosBackgroundAudioValue, "Audio Background Capability"),
560                        "Enable this to grant your app the capability to access audio when in background mode. "
561                        "This permission is required if your app creates a MIDI input or output device.");
562 
563             props.add (new ChoicePropertyComponent (iosBackgroundBleValue, "Bluetooth MIDI Background Capability"),
564                        "Enable this to grant your app the capability to connect to Bluetooth LE devices when in background mode.");
565 
566             props.add (new ChoicePropertyComponent (iosAppGroupsValue, "App Groups Capability"),
567                        "Enable this to grant your app the capability to share resources between apps using the same app group ID.");
568 
569             props.add (new ChoicePropertyComponent (iCloudPermissionsValue, "iCloud Permissions"),
570                        "Enable this to grant your app the capability to use native file load/save browser windows on iOS.");
571         }
572 
573         props.add (new ChoicePropertyComponent (iosPushNotificationsValue, "Push Notifications Capability"),
574                    "Enable this to grant your app the capability to receive push notifications.");
575 
576         props.add (new TextPropertyComponent (customPListValue, "Custom PList", 8192, true),
577                    "You can paste the contents of an XML PList file in here, and the settings that it contains will override any "
578                    "settings that the Projucer creates. BEWARE! When doing this, be careful to remove from the XML any "
579                    "values that you DO want the Projucer to change!");
580 
581         props.add (new ChoicePropertyComponent (pListPreprocessValue, "PList Preprocess"),
582                    "Enable this to preprocess PList file. This will allow you to set values to preprocessor defines,"
583                    " for instance if you define: #define MY_FLAG 1 in a prefix header file (see PList prefix header), you can have"
584                    " a key with MY_FLAG value and it will be replaced with 1.");
585 
586         props.add (new TextPropertyComponent (pListPrefixHeaderValue, "PList Prefix Header", 512, false),
587                    "Header file containing definitions used in plist file (see PList Preprocess).");
588 
589         props.add (new ChoicePropertyComponent (suppressPlistResourceUsageValue, "Suppress AudioUnit Plist resourceUsage Key"),
590                    "Suppress the resourceUsage key in the target's generated Plist. This is useful for AU"
591                    " plugins that must access resources which cannot be declared in the resourceUsage block, such"
592                    " as UNIX domain sockets. In particular, PACE-protected AU plugins may require this option to be enabled"
593                    " in order for the plugin to load in GarageBand.");
594 
595         props.add (new TextPropertyComponent (extraFrameworksValue, "Extra System Frameworks", 2048, false),
596                    "A comma-separated list of extra system frameworks that should be added to the build. "
597                    "(Don't include the .framework extension in the name)"
598                    " The frameworks are expected to be located in /System/Library/Frameworks");
599 
600         props.add (new TextPropertyComponent (frameworkSearchPathsValue, "Framework Search Paths", 8192, true),
601                    "A set of paths to search for custom frameworks (one per line).");
602 
603         props.add (new TextPropertyComponent (extraCustomFrameworksValue, "Extra Custom Frameworks", 8192, true),
604                    "Paths to custom frameworks that should be added to the build (one per line). "
605                    "You will probably need to add an entry to the Framework Search Paths for each unique directory.");
606 
607         props.add (new TextPropertyComponent (embeddedFrameworksValue, "Embedded Frameworks", 8192, true),
608                    "Paths to frameworks to be embedded with the app (one per line). "
609                    "If you are adding a framework here then you do not need to specify it in Extra Custom Frameworks too. "
610                    "You will probably need to add an entry to the Framework Search Paths for each unique directory.");
611 
612         props.add (new TextPropertyComponent (subprojectsValue, "Xcode Subprojects", 8192, true),
613                    "Paths to Xcode projects that should be added to the build (one per line). "
614                    "These can be absolute or relative to the build directory. "
615                    "The names of the required build products can be specified after a colon, comma separated, "
616                    "e.g. \"path/to/MySubProject.xcodeproj: MySubProject, OtherTarget\". "
617                    "If no build products are specified, all build products associated with a subproject will be added.");
618 
619         props.add (new TextPropertyComponent (prebuildCommandValue, "Pre-Build Shell Script", 32768, true),
620                    "Some shell-script that will be run before a build starts.");
621 
622         props.add (new TextPropertyComponent (postbuildCommandValue, "Post-Build Shell Script", 32768, true),
623                    "Some shell-script that will be run after a build completes.");
624 
625         props.add (new TextPropertyComponent (exporterBundleIdentifierValue, "Exporter Bundle Identifier", 256, false),
626                    "Use this to override the project bundle identifier for this exporter. "
627                    "This is useful if you want to use different bundle identifiers for Mac and iOS exporters in the same project.");
628 
629         props.add (new TextPropertyComponent (iosDevelopmentTeamIDValue, "Development Team ID", 10, false),
630                    "The Development Team ID to be used for setting up code-signing your app. This is a ten-character "
631                    "string (for example, \"S7B6T5XJ2Q\") that describes the distribution certificate Apple issued to you. "
632                    "You can find this string in the OS X app Keychain Access under \"Certificates\".");
633 
634         if (iOS)
635             props.add (new TextPropertyComponentWithEnablement (iosAppGroupsIDValue, iosAppGroupsValue, "App Group ID", 256, false),
636                        "The App Group ID to be used for allowing multiple apps to access a shared resource folder. Multiple IDs can be "
637                        "added separated by a semicolon.");
638 
639         props.add (new ChoicePropertyComponent (keepCustomXcodeSchemesValue, "Keep Custom Xcode Schemes"),
640                    "Enable this to keep any Xcode schemes you have created for debugging or running, e.g. to launch a plug-in in"
641                    "various hosts. If disabled, all schemes are replaced by a default set.");
642 
643         props.add (new ChoicePropertyComponent (useHeaderMapValue, "USE_HEADERMAP"),
644                    "Enable this to make Xcode search all the projects folders for include files. This means you can be lazy "
645                    "and not bother using relative paths to include your headers, but it means your code won't be "
646                    "compatible with other build systems");
647     }
648 
launchProject()649     bool launchProject() override
650     {
651        #if JUCE_MAC
652         return getProjectBundle().startAsProcess();
653        #else
654         return false;
655        #endif
656     }
657 
canLaunchProject()658     bool canLaunchProject() override
659     {
660        #if JUCE_MAC
661         return true;
662        #else
663         return false;
664        #endif
665     }
666 
667     //==============================================================================
create(const OwnedArray<LibraryModule> &)668     void create (const OwnedArray<LibraryModule>&) const override
669     {
670         for (auto& target : targets)
671             if (target->shouldCreatePList())
672                 target->infoPlistFile = getTargetFolder().getChildFile (target->getInfoPlistName());
673 
674         menuNibFile = getTargetFolder().getChildFile ("RecentFilesMenuTemplate.nib");
675 
676         createIconFile();
677 
678         auto projectBundle = getProjectBundle();
679         createDirectoryOrThrow (projectBundle);
680 
681         createObjects();
682 
683         build_tools::writeStreamToFile (projectBundle.getChildFile ("project.pbxproj"),
684                                         [this] (MemoryOutputStream& mo) { writeProjectFile (mo); });
685 
686         writeInfoPlistFiles();
687         writeWorkspaceSettings();
688 
689         // Deleting the .rsrc files can be needed to force Xcode to update the version number.
690         deleteRsrcFiles (getTargetFolder().getChildFile ("build"));
691     }
692 
693     //==============================================================================
addPlatformSpecificSettingsForProjectType(const build_tools::ProjectType &)694     void addPlatformSpecificSettingsForProjectType (const build_tools::ProjectType&) override
695     {
696         callForAllSupportedTargets ([this] (build_tools::ProjectType::Target::Type targetType)
697                                     {
698                                         targets.insert (targetType == build_tools::ProjectType::Target::AggregateTarget ? 0 : -1,
699                                                         new XcodeTarget (targetType, *this));
700                                     });
701 
702         // If you hit this assert, you tried to generate a project for an exporter
703         // that does not support any of your targets!
704         jassert (targets.size() > 0);
705     }
706 
updateDeprecatedSettings()707     void updateDeprecatedSettings() override
708     {
709         if (iOS)
710             updateOldOrientationSettings();
711     }
712 
updateDeprecatedSettingsInteractively()713     void updateDeprecatedSettingsInteractively() override
714     {
715         if (hasInvalidPostBuildScript())
716         {
717             String alertWindowText = iOS ? "Your Xcode (iOS) Exporter settings use an invalid post-build script. Click 'Update' to remove it."
718                                          : "Your Xcode (macOS) Exporter settings use a pre-JUCE 4.2 post-build script to move the plug-in binaries to their plug-in install folders.\n\n"
719                                            "Since JUCE 4.2, this is instead done using \"AU/VST/VST2/AAX/RTAS Binary Location\" in the Xcode (OS X) configuration settings.\n\n"
720                                            "Click 'Update' to remove the script (otherwise your plug-in may not compile correctly).";
721 
722             if (AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
723                                               "Project settings: " + project.getDocumentTitle(),
724                                               alertWindowText, "Update", "Cancel", nullptr, nullptr))
725                 postbuildCommandValue.resetToDefault();
726         }
727     }
728 
hasInvalidPostBuildScript()729     bool hasInvalidPostBuildScript() const
730     {
731         // check whether the script is identical to the old one that the Introjucer used to auto-generate
732         return (MD5 (getPostBuildScript().toUTF8()).toHexString() == "265ac212a7e734c5bbd6150e1eae18a1");
733     }
734 
735     //==============================================================================
initialiseDependencyPathValues()736     void initialiseDependencyPathValues() override
737     {
738         vstLegacyPathValueWrapper.init ({ settings, Ids::vstLegacyFolder, nullptr },
739                                         getAppSettings().getStoredPath (Ids::vstLegacyPath, TargetOS::osx), TargetOS::osx);
740 
741         aaxPathValueWrapper.init ({ settings, Ids::aaxFolder, nullptr },
742                                   getAppSettings().getStoredPath (Ids::aaxPath,  TargetOS::osx), TargetOS::osx);
743 
744         rtasPathValueWrapper.init ({ settings, Ids::rtasFolder, nullptr },
745                                    getAppSettings().getStoredPath (Ids::rtasPath, TargetOS::osx), TargetOS::osx);
746     }
747 
748 protected:
749     //==============================================================================
750     class XcodeBuildConfiguration  : public BuildConfiguration
751     {
752     public:
XcodeBuildConfiguration(Project & p,const ValueTree & t,const bool isIOS,const ProjectExporter & e)753         XcodeBuildConfiguration (Project& p, const ValueTree& t, const bool isIOS, const ProjectExporter& e)
754             : BuildConfiguration (p, t, e),
755               iOS (isIOS),
756               osxSDKVersion                (config, Ids::osxSDK,                       getUndoManager()),
757               osxDeploymentTarget          (config, Ids::osxCompatibility,             getUndoManager(), getDisplayName (macOSDefaultVersion)),
758               iosDeploymentTarget          (config, Ids::iosCompatibility,             getUndoManager(), iOSDefaultVersion),
759               osxArchitecture              (config, Ids::osxArchitecture,              getUndoManager(), osxArch_Default),
760               customXcodeFlags             (config, Ids::customXcodeFlags,             getUndoManager()),
761               plistPreprocessorDefinitions (config, Ids::plistPreprocessorDefinitions, getUndoManager()),
762               codeSignIdentity             (config, Ids::codeSigningIdentity,          getUndoManager()),
763               fastMathEnabled              (config, Ids::fastMath,                     getUndoManager()),
764               stripLocalSymbolsEnabled     (config, Ids::stripLocalSymbols,            getUndoManager()),
765               pluginBinaryCopyStepEnabled  (config, Ids::enablePluginBinaryCopyStep,   getUndoManager(), true),
766               vstBinaryLocation            (config, Ids::vstBinaryLocation,            getUndoManager(), "$(HOME)/Library/Audio/Plug-Ins/VST/"),
767               vst3BinaryLocation           (config, Ids::vst3BinaryLocation,           getUndoManager(), "$(HOME)/Library/Audio/Plug-Ins/VST3/"),
768               auBinaryLocation             (config, Ids::auBinaryLocation,             getUndoManager(), "$(HOME)/Library/Audio/Plug-Ins/Components/"),
769               rtasBinaryLocation           (config, Ids::rtasBinaryLocation,           getUndoManager(), "/Library/Application Support/Digidesign/Plug-Ins/"),
770               aaxBinaryLocation            (config, Ids::aaxBinaryLocation,            getUndoManager(), "/Library/Application Support/Avid/Audio/Plug-Ins/"),
771               unityPluginBinaryLocation    (config, Ids::unityPluginBinaryLocation,    getUndoManager())
772         {
773             updateOldPluginBinaryLocations();
774             updateOldSDKDefaults();
775 
776             optimisationLevelValue.setDefault (isDebug() ? gccO0 : gccO3);
777         }
778 
779         //==============================================================================
createConfigProperties(PropertyListBuilder & props)780         void createConfigProperties (PropertyListBuilder& props) override
781         {
782             if (project.isAudioPluginProject())
783                 addXcodePluginInstallPathProperties (props);
784 
785             addRecommendedLLVMCompilerWarningsProperty (props);
786             addGCCOptimisationProperty (props);
787 
788             if (iOS)
789             {
790                 Array<var> iOSVersionVars;
791 
792                 for (auto& s : iOSVersions)
793                     iOSVersionVars.add (s);
794 
795                 props.add (new ChoicePropertyComponent (iosDeploymentTarget, "iOS Deployment Target", iOSVersions, iOSVersionVars),
796                            "The minimum version of iOS that the target binary will run on.");
797             }
798             else
799             {
800                 props.add (new ChoicePropertyComponent (osxSDKVersion, "macOS Base SDK Version", getSDKChoiceList<StringArray> (oldestSDKVersion, true),
801                                                                                                  getSDKChoiceList<Array<var>>  (oldestSDKVersion, true)),
802                            "The version of the macOS SDK to link against. If \"Default\" is selected then the Xcode default will be used.");
803 
804                 props.add (new ChoicePropertyComponent (osxDeploymentTarget, "macOS Deployment Target", getSDKChoiceList<StringArray> (oldestDeploymentTarget, false),
805                                                                                                         getSDKChoiceList<Array<var>>  (oldestDeploymentTarget, true)),
806                            "The minimum version of macOS that the target binary will be compatible with.");
807 
808                 props.add (new ChoicePropertyComponent (osxArchitecture, "macOS Architecture",
809                                                         { "Native architecture of build machine", "Standard 32-bit",        "Standard 32/64-bit",   "Standard 64-bit" },
810                                                         { osxArch_Native,                          osxArch_32BitUniversal,  osxArch_64BitUniversal, osxArch_64Bit }),
811                            "The type of macOS binary that will be produced.");
812             }
813 
814             props.add (new TextPropertyComponent (customXcodeFlags, "Custom Xcode Flags", 8192, true),
815                        "A comma-separated list of custom Xcode setting flags which will be appended to the list of generated flags, "
816                        "e.g. MACOSX_DEPLOYMENT_TARGET_i386 = 10.5");
817 
818             props.add (new TextPropertyComponent (plistPreprocessorDefinitions, "PList Preprocessor Definitions", 2048, true),
819                        "Preprocessor definitions used during PList preprocessing (see PList Preprocess).");
820 
821             props.add (new TextPropertyComponent (codeSignIdentity, "Code-Signing Identity", 1024, false),
822                        "The name of a code-signing identity for Xcode to apply.");
823 
824             props.add (new ChoicePropertyComponent (fastMathEnabled, "Relax IEEE Compliance"),
825                        "Enable this to use FAST_MATH non-IEEE mode. (Warning: this can have unexpected results!)");
826 
827             props.add (new ChoicePropertyComponent (stripLocalSymbolsEnabled, "Strip Local Symbols"),
828                        "Enable this to strip any locally defined symbols resulting in a smaller binary size. Enabling this "
829                        "will also remove any function names from crash logs. Must be disabled for static library projects. "
830                        "Note that disabling this will not necessarily generate full debug symbols. For release configs, "
831                        "you will also need to add the following to the \"Custom Xcode Flags\" field: "
832                        "GCC_GENERATE_DEBUGGING_SYMBOLS = YES, STRIP_INSTALLED_PRODUCT = NO, COPY_PHASE_STRIP = NO");
833         }
834 
getModuleLibraryArchName()835         String getModuleLibraryArchName() const override
836         {
837             return "${CURRENT_ARCH}";
838         }
839 
840         //==============================================================================
getOSXArchitectureString()841         String getOSXArchitectureString() const                 { return osxArchitecture.get(); }
getPListPreprocessorDefinitionsString()842         String getPListPreprocessorDefinitionsString() const    { return plistPreprocessorDefinitions.get(); }
843 
isFastMathEnabled()844         bool isFastMathEnabled() const                          { return fastMathEnabled.get(); }
845 
isStripLocalSymbolsEnabled()846         bool isStripLocalSymbolsEnabled() const                 { return stripLocalSymbolsEnabled.get(); }
847 
getCustomXcodeFlagsString()848         String getCustomXcodeFlagsString() const                { return customXcodeFlags.get(); }
849 
getOSXSDKVersionString()850         String getOSXSDKVersionString() const                   { return osxSDKVersion.get(); }
getOSXDeploymentTargetString()851         String getOSXDeploymentTargetString() const             { return osxDeploymentTarget.get(); }
852 
getCodeSignIdentityString()853         String getCodeSignIdentityString() const                { return codeSignIdentity.get(); }
854 
getiOSDeploymentTargetString()855         String getiOSDeploymentTargetString() const             { return iosDeploymentTarget.get(); }
856 
isPluginBinaryCopyStepEnabled()857         bool isPluginBinaryCopyStepEnabled() const              { return pluginBinaryCopyStepEnabled.get(); }
getVSTBinaryLocationString()858         String getVSTBinaryLocationString() const               { return vstBinaryLocation.get(); }
getVST3BinaryLocationString()859         String getVST3BinaryLocationString() const              { return vst3BinaryLocation.get(); }
getAUBinaryLocationString()860         String getAUBinaryLocationString() const                { return auBinaryLocation.get(); }
getRTASBinaryLocationString()861         String getRTASBinaryLocationString() const              { return rtasBinaryLocation.get();}
getAAXBinaryLocationString()862         String getAAXBinaryLocationString() const               { return aaxBinaryLocation.get();}
getUnityPluginBinaryLocationString()863         String getUnityPluginBinaryLocationString() const       { return unityPluginBinaryLocation.get(); }
864 
865     private:
866         //==============================================================================
867         bool iOS;
868 
869         ValueWithDefault osxSDKVersion, osxDeploymentTarget, iosDeploymentTarget, osxArchitecture,
870                          customXcodeFlags, plistPreprocessorDefinitions, codeSignIdentity,
871                          fastMathEnabled, stripLocalSymbolsEnabled, pluginBinaryCopyStepEnabled,
872                          vstBinaryLocation, vst3BinaryLocation, auBinaryLocation, rtasBinaryLocation,
873                          aaxBinaryLocation, unityPluginBinaryLocation;
874 
875         //==============================================================================
addXcodePluginInstallPathProperties(PropertyListBuilder & props)876         void addXcodePluginInstallPathProperties (PropertyListBuilder& props)
877         {
878             auto isBuildingAnyPlugins = (project.shouldBuildVST() || project.shouldBuildVST3() || project.shouldBuildAU()
879                                          || project.shouldBuildRTAS() || project.shouldBuildAAX() || project.shouldBuildUnityPlugin());
880 
881             if (isBuildingAnyPlugins)
882                 props.add (new ChoicePropertyComponent (pluginBinaryCopyStepEnabled, "Enable Plugin Copy Step"),
883                            "Enable this to copy plugin binaries to the specified folder after building.");
884 
885             if (project.shouldBuildVST3())
886                 props.add (new TextPropertyComponentWithEnablement (vst3BinaryLocation, pluginBinaryCopyStepEnabled, "VST3 Binary Location",
887                                                                     1024, false),
888                            "The folder in which the compiled VST3 binary should be placed.");
889 
890             if (project.shouldBuildAU())
891                 props.add (new TextPropertyComponentWithEnablement (auBinaryLocation, pluginBinaryCopyStepEnabled, "AU Binary Location",
892                                                                     1024, false),
893                            "The folder in which the compiled AU binary should be placed.");
894 
895             if (project.shouldBuildRTAS())
896                 props.add (new TextPropertyComponentWithEnablement (rtasBinaryLocation, pluginBinaryCopyStepEnabled, "RTAS Binary Location",
897                                                                     1024, false),
898                            "The folder in which the compiled RTAS binary should be placed.");
899 
900             if (project.shouldBuildAAX())
901                 props.add (new TextPropertyComponentWithEnablement (aaxBinaryLocation, pluginBinaryCopyStepEnabled, "AAX Binary Location",
902                                                                     1024, false),
903                            "The folder in which the compiled AAX binary should be placed.");
904 
905             if (project.shouldBuildUnityPlugin())
906                 props.add (new TextPropertyComponentWithEnablement (unityPluginBinaryLocation, pluginBinaryCopyStepEnabled, "Unity Binary Location",
907                                                                     1024, false),
908                            "The folder in which the compiled Unity plugin binary and associated C# GUI script should be placed.");
909 
910             if (project.shouldBuildVST())
911                 props.add (new TextPropertyComponentWithEnablement (vstBinaryLocation, pluginBinaryCopyStepEnabled, "VST Binary Location",
912                                                                     1024, false),
913                            "The folder in which the compiled legacy VST binary should be placed.");
914         }
915 
updateOldPluginBinaryLocations()916         void updateOldPluginBinaryLocations()
917         {
918             if (! config ["xcodeVstBinaryLocation"].isVoid())        vstBinaryLocation  = config ["xcodeVstBinaryLocation"];
919             if (! config ["xcodeVst3BinaryLocation"].isVoid())       vst3BinaryLocation = config ["xcodeVst3BinaryLocation"];
920             if (! config ["xcodeAudioUnitBinaryLocation"].isVoid())  auBinaryLocation   = config ["xcodeAudioUnitBinaryLocation"];
921             if (! config ["xcodeRtasBinaryLocation"].isVoid())       rtasBinaryLocation = config ["xcodeRtasBinaryLocation"];
922             if (! config ["xcodeAaxBinaryLocation"].isVoid())        aaxBinaryLocation  = config ["xcodeAaxBinaryLocation"];
923         }
924 
updateOldSDKDefaults()925         void updateOldSDKDefaults()
926         {
927             if (iosDeploymentTarget.get() == "default")    iosDeploymentTarget.resetToDefault();
928             if (osxArchitecture.get() == "default")        osxArchitecture.resetToDefault();
929             if (osxSDKVersion.get() == "default")          osxSDKVersion.resetToDefault();
930             if (osxDeploymentTarget.get() == "default")    osxDeploymentTarget.resetToDefault();
931         }
932     };
933 
createBuildConfig(const ValueTree & v)934     BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const override
935     {
936         return *new XcodeBuildConfiguration (project, v, iOS, *this);
937     }
938 
939 public:
940     //==============================================================================
941     /* The numbers for these enum values are defined by Xcode for the different
942        possible destinations of a "copy files" post-build step.
943     */
944     enum XcodeCopyFilesDestinationIDs
945     {
946         kWrapperFolder          = 1,
947         kExecutablesFolder      = 6,
948         kResourcesFolder        = 7,
949         kFrameworksFolder       = 10,
950         kSharedFrameworksFolder = 11,
951         kSharedSupportFolder    = 12,
952         kPluginsFolder          = 13,
953         kJavaResourcesFolder    = 15,
954         kXPCServicesFolder      = 16
955     };
956 
957     //==============================================================================
958     struct XcodeTarget : build_tools::ProjectType::Target
959     {
960         //==============================================================================
XcodeTargetXcodeTarget961         XcodeTarget (build_tools::ProjectType::Target::Type targetType, const XcodeProjectExporter& exporter)
962             : Target (targetType),
963               owner (exporter)
964         {
965             switch (type)
966             {
967                 case GUIApp:
968                     xcodeFileType = "wrapper.application";
969                     xcodeBundleExtension = ".app";
970                     xcodeProductType = "com.apple.product-type.application";
971                     xcodeCopyToProductInstallPathAfterBuild = false;
972                     break;
973 
974                 case ConsoleApp:
975                     xcodeFileType = "compiled.mach-o.executable";
976                     xcodeBundleExtension = String();
977                     xcodeProductType = "com.apple.product-type.tool";
978                     xcodeCopyToProductInstallPathAfterBuild = false;
979                     break;
980 
981                 case StaticLibrary:
982                     xcodeFileType = "archive.ar";
983                     xcodeBundleExtension = ".a";
984                     xcodeProductType = "com.apple.product-type.library.static";
985                     xcodeCopyToProductInstallPathAfterBuild = false;
986                     break;
987 
988                 case DynamicLibrary:
989                     xcodeFileType = "compiled.mach-o.dylib";
990                     xcodeProductType = "com.apple.product-type.library.dynamic";
991                     xcodeBundleExtension = ".dylib";
992                     xcodeCopyToProductInstallPathAfterBuild = false;
993                     break;
994 
995                 case VSTPlugIn:
996                     xcodeFileType = "wrapper.cfbundle";
997                     xcodeBundleExtension = ".vst";
998                     xcodeProductType = "com.apple.product-type.bundle";
999                     xcodeCopyToProductInstallPathAfterBuild = true;
1000                     break;
1001 
1002                 case VST3PlugIn:
1003                     xcodeFileType = "wrapper.cfbundle";
1004                     xcodeBundleExtension = ".vst3";
1005                     xcodeProductType = "com.apple.product-type.bundle";
1006                     xcodeCopyToProductInstallPathAfterBuild = true;
1007                     break;
1008 
1009                 case AudioUnitPlugIn:
1010                     xcodeFileType = "wrapper.cfbundle";
1011                     xcodeBundleExtension = ".component";
1012                     xcodeProductType = "com.apple.product-type.bundle";
1013                     xcodeCopyToProductInstallPathAfterBuild = true;
1014 
1015                     addExtraAudioUnitTargetSettings();
1016                     break;
1017 
1018                 case StandalonePlugIn:
1019                     xcodeFileType = "wrapper.application";
1020                     xcodeBundleExtension = ".app";
1021                     xcodeProductType = "com.apple.product-type.application";
1022                     xcodeCopyToProductInstallPathAfterBuild = false;
1023                     break;
1024 
1025                 case AudioUnitv3PlugIn:
1026                     xcodeFileType = "wrapper.app-extension";
1027                     xcodeBundleExtension = ".appex";
1028                     xcodeBundleIDSubPath = "AUv3";
1029                     xcodeProductType = "com.apple.product-type.app-extension";
1030                     xcodeCopyToProductInstallPathAfterBuild = false;
1031 
1032                     addExtraAudioUnitv3PlugInTargetSettings();
1033                     break;
1034 
1035                 case AAXPlugIn:
1036                     xcodeFileType = "wrapper.cfbundle";
1037                     xcodeBundleExtension = ".aaxplugin";
1038                     xcodeProductType = "com.apple.product-type.bundle";
1039                     xcodeCopyToProductInstallPathAfterBuild = true;
1040                     break;
1041 
1042                 case RTASPlugIn:
1043                     xcodeFileType = "wrapper.cfbundle";
1044                     xcodeBundleExtension = ".dpm";
1045                     xcodeProductType = "com.apple.product-type.bundle";
1046                     xcodeCopyToProductInstallPathAfterBuild = true;
1047                     break;
1048 
1049                 case UnityPlugIn:
1050                     xcodeFileType = "wrapper.cfbundle";
1051                     xcodeBundleExtension = ".bundle";
1052                     xcodeProductType = "com.apple.product-type.bundle";
1053                     xcodeCopyToProductInstallPathAfterBuild = true;
1054                     break;
1055 
1056                 case SharedCodeTarget:
1057                     xcodeFileType = "archive.ar";
1058                     xcodeBundleExtension = ".a";
1059                     xcodeProductType = "com.apple.product-type.library.static";
1060                     xcodeCopyToProductInstallPathAfterBuild = false;
1061                     break;
1062 
1063                 case AggregateTarget:
1064                     xcodeCopyToProductInstallPathAfterBuild = false;
1065                     break;
1066 
1067                 case unspecified:
1068                 default:
1069                     // unknown target type!
1070                     jassertfalse;
1071                     break;
1072             }
1073         }
1074 
getXcodeSchemeNameXcodeTarget1075         String getXcodeSchemeName() const
1076         {
1077             return owner.projectName + " - " + getName();
1078         }
1079 
getIDXcodeTarget1080         String getID() const
1081         {
1082             return owner.createID (String ("__target") + getName());
1083         }
1084 
getInfoPlistNameXcodeTarget1085         String getInfoPlistName() const
1086         {
1087             return String ("Info-") + String (getName()).replace (" ", "_") + String (".plist");
1088         }
1089 
getEntitlementsFilenameXcodeTarget1090         String getEntitlementsFilename() const
1091         {
1092             return String (getName()).replace (" ", "_") + String (".entitlements");
1093         }
1094 
1095         String xcodeBundleExtension;
1096         String xcodeProductType, xcodeFileType;
1097         String xcodeOtherRezFlags, xcodeBundleIDSubPath;
1098         bool xcodeCopyToProductInstallPathAfterBuild;
1099         StringArray xcodeFrameworks, xcodeLibs;
1100         Array<XmlElement> xcodeExtraPListEntries;
1101 
1102         StringArray frameworkIDs, buildPhaseIDs, configIDs, sourceIDs, rezFileIDs, dependencyIDs;
1103         StringArray frameworkNames;
1104         String mainBuildProductID;
1105         File infoPlistFile;
1106 
1107         struct SourceFileInfo
1108         {
1109             build_tools::RelativePath path;
1110             bool shouldBeCompiled = false;
1111         };
1112 
getSourceFilesInfoXcodeTarget1113         Array<SourceFileInfo> getSourceFilesInfo (const Project::Item& projectItem) const
1114         {
1115             Array<SourceFileInfo> result;
1116 
1117             auto targetType = (owner.getProject().isAudioPluginProject() ? type : SharedCodeTarget);
1118 
1119             if (projectItem.isGroup())
1120             {
1121                 for (int i = 0; i < projectItem.getNumChildren(); ++i)
1122                     result.addArray (getSourceFilesInfo (projectItem.getChild (i)));
1123             }
1124             else if (projectItem.shouldBeAddedToTargetProject() && projectItem.shouldBeAddedToTargetExporter (owner)
1125                      && owner.getProject().getTargetTypeFromFilePath (projectItem.getFile(), true) == targetType)
1126             {
1127                 SourceFileInfo info;
1128 
1129                 info.path = build_tools::RelativePath (projectItem.getFile(),
1130                                                        owner.getTargetFolder(),
1131                                                        build_tools::RelativePath::buildTargetFolder);
1132 
1133                 jassert (info.path.getRoot() == build_tools::RelativePath::buildTargetFolder);
1134 
1135                 if (targetType == SharedCodeTarget || projectItem.shouldBeCompiled())
1136                     info.shouldBeCompiled = projectItem.shouldBeCompiled();
1137 
1138                 result.add (info);
1139             }
1140 
1141             return result;
1142         }
1143 
1144         //==============================================================================
addMainBuildProductXcodeTarget1145         void addMainBuildProduct() const
1146         {
1147             jassert (xcodeFileType.isNotEmpty());
1148             jassert (xcodeBundleExtension.isEmpty() || xcodeBundleExtension.startsWithChar ('.'));
1149 
1150             if (ProjectExporter::BuildConfiguration::Ptr config = owner.getConfiguration (0))
1151             {
1152                 auto productName = owner.replacePreprocessorTokens (*config, config->getTargetBinaryNameString (type == UnityPlugIn));
1153 
1154                 if (xcodeFileType == "archive.ar")
1155                     productName = getStaticLibbedFilename (productName);
1156                 else
1157                     productName += xcodeBundleExtension;
1158 
1159                 addBuildProduct (xcodeFileType, productName);
1160             }
1161         }
1162 
1163         //==============================================================================
addBuildProductXcodeTarget1164         void addBuildProduct (const String& fileType, const String& binaryName) const
1165         {
1166             ValueTree v (owner.createID (String ("__productFileID") + getName()) + " /* " + getName() + " */");
1167             v.setProperty ("isa", "PBXFileReference", nullptr);
1168             v.setProperty ("explicitFileType", fileType, nullptr);
1169             v.setProperty ("includeInIndex", (int) 0, nullptr);
1170             v.setProperty ("path", binaryName, nullptr);
1171             v.setProperty ("sourceTree", "BUILT_PRODUCTS_DIR", nullptr);
1172 
1173             owner.addObject (v);
1174         }
1175 
1176         //==============================================================================
addDependencyForXcodeTarget1177         String addDependencyFor (const XcodeTarget& dependentTarget)
1178         {
1179             auto dependencyID = owner.createID (String ("__dependency") + getName() + dependentTarget.getName());
1180             ValueTree v (dependencyID);
1181             v.setProperty ("isa", "PBXTargetDependency", nullptr);
1182             v.setProperty ("target", getID(), nullptr);
1183 
1184             owner.addObject (v);
1185 
1186             return dependencyID;
1187         }
1188 
addDependenciesXcodeTarget1189         void addDependencies()
1190         {
1191             if (! owner.project.isAudioPluginProject())
1192                 return;
1193 
1194             if (type == XcodeTarget::StandalonePlugIn) // depends on AUv3 and shared code
1195             {
1196                 if (auto* auv3Target = owner.getTargetOfType (XcodeTarget::AudioUnitv3PlugIn))
1197                     dependencyIDs.add (auv3Target->addDependencyFor (*this));
1198 
1199                 if (auto* sharedCodeTarget = owner.getTargetOfType (XcodeTarget::SharedCodeTarget))
1200                     dependencyIDs.add (sharedCodeTarget->addDependencyFor (*this));
1201             }
1202             else if (type == XcodeTarget::AggregateTarget) // depends on all other targets
1203             {
1204                 for (auto* target : owner.targets)
1205                     if (target->type != XcodeTarget::AggregateTarget)
1206                         dependencyIDs.add (target->addDependencyFor (*this));
1207             }
1208             else if (type != XcodeTarget::SharedCodeTarget) // shared code doesn't depend on anything; all other targets depend only on the shared code
1209             {
1210                 if (auto* sharedCodeTarget = owner.getTargetOfType (XcodeTarget::SharedCodeTarget))
1211                     dependencyIDs.add (sharedCodeTarget->addDependencyFor (*this));
1212             }
1213         }
1214 
1215         //==============================================================================
addTargetConfigXcodeTarget1216         void addTargetConfig (const String& configName, const StringArray& buildSettings)
1217         {
1218             auto configID = owner.createID (String ("targetconfigid_") + getName() + String ("_") + configName);
1219 
1220             ValueTree v (configID);
1221             v.setProperty ("isa", "XCBuildConfiguration", nullptr);
1222             v.setProperty ("buildSettings", indentBracedList (buildSettings), nullptr);
1223             v.setProperty (Ids::name, configName, nullptr);
1224 
1225             configIDs.add (configID);
1226 
1227             owner.addObject (v);
1228         }
1229 
1230         //==============================================================================
getTargetAttributesXcodeTarget1231         String getTargetAttributes() const
1232         {
1233             StringArray attributes;
1234 
1235             auto developmentTeamID = owner.getDevelopmentTeamIDString();
1236 
1237             if (developmentTeamID.isNotEmpty())
1238             {
1239                 attributes.add ("DevelopmentTeam = " + developmentTeamID);
1240                 attributes.add ("ProvisioningStyle = Automatic");
1241             }
1242 
1243             std::map<String, bool> capabilities;
1244 
1245             capabilities["ApplicationGroups.iOS"] = owner.iOS && owner.isAppGroupsEnabled();
1246             capabilities["InAppPurchase"]         = owner.isInAppPurchasesEnabled();
1247             capabilities["InterAppAudio"]         = owner.iOS && type == Target::StandalonePlugIn && owner.getProject().shouldEnableIAA();
1248             capabilities["Push"]                  = owner.isPushNotificationsEnabled();
1249             capabilities["Sandbox"]               = type == Target::AudioUnitv3PlugIn || owner.isAppSandboxEnabled();
1250             capabilities["HardenedRuntime"]       = owner.isHardenedRuntimeEnabled();
1251 
1252             if (owner.iOS && owner.isiCloudPermissionsEnabled())
1253                 capabilities["com.apple.iCloud"] = true;
1254 
1255             StringArray capabilitiesStrings;
1256 
1257             for (auto& capability : capabilities)
1258                 capabilitiesStrings.add ("com.apple." + capability.first + " = " + indentBracedList ({ String ("enabled = ") + (capability.second ? "1" : "0") }, 4));
1259 
1260             attributes.add ("SystemCapabilities = " + indentBracedList (capabilitiesStrings, 3));
1261 
1262             attributes.sort (false);
1263 
1264             return getID() + " = " + indentBracedList (attributes, 2);
1265         }
1266 
1267         //==============================================================================
1268         ValueTree addBuildPhase (const String& buildPhaseType, const StringArray& fileIds, const StringRef humanReadableName = StringRef())
1269         {
1270             auto buildPhaseName = buildPhaseType + "_" + getName() + "_" + (humanReadableName.isNotEmpty() ? String (humanReadableName) : String ("resbuildphase"));
1271             auto buildPhaseId (owner.createID (buildPhaseName));
1272 
1273             int n = 0;
1274             while (buildPhaseIDs.contains (buildPhaseId))
1275                 buildPhaseId = owner.createID (buildPhaseName + String (++n));
1276 
1277             buildPhaseIDs.add (buildPhaseId);
1278 
1279             ValueTree v (buildPhaseId);
1280             v.setProperty ("isa", buildPhaseType, nullptr);
1281             v.setProperty ("buildActionMask", "2147483647", nullptr);
1282             v.setProperty ("files", indentParenthesisedList (fileIds), nullptr);
1283 
1284             if (humanReadableName.isNotEmpty())
1285                 v.setProperty ("name", String (humanReadableName), nullptr);
1286 
1287             v.setProperty ("runOnlyForDeploymentPostprocessing", (int) 0, nullptr);
1288 
1289             owner.addObject (v);
1290 
1291             return v;
1292         }
1293 
shouldCreatePListXcodeTarget1294         bool shouldCreatePList() const
1295         {
1296             auto fileType = getTargetFileType();
1297             return (fileType == executable && type != ConsoleApp) || fileType == pluginBundle || fileType == macOSAppex;
1298         }
1299 
1300         //==============================================================================
shouldAddEntitlementsXcodeTarget1301         bool shouldAddEntitlements() const
1302         {
1303             if (owner.isPushNotificationsEnabled()
1304              || owner.isAppGroupsEnabled()
1305              || owner.isAppSandboxEnabled()
1306              || owner.isHardenedRuntimeEnabled()
1307              || (owner.isiOS() && owner.isiCloudPermissionsEnabled()))
1308                 return true;
1309 
1310             if (owner.project.isAudioPluginProject()
1311                 && ((owner.isOSX() && type == Target::AudioUnitv3PlugIn)
1312                     || (owner.isiOS() && type == Target::StandalonePlugIn && owner.getProject().shouldEnableIAA())))
1313                 return true;
1314 
1315             return false;
1316         }
1317 
getBundleIdentifierXcodeTarget1318         String getBundleIdentifier() const
1319         {
1320             auto exporterBundleIdentifier = owner.exporterBundleIdentifierValue.get().toString();
1321             auto bundleIdentifier = exporterBundleIdentifier.isNotEmpty() ? exporterBundleIdentifier
1322                                                                           : owner.project.getBundleIdentifierString();
1323 
1324             if (xcodeBundleIDSubPath.isNotEmpty())
1325             {
1326                 auto bundleIdSegments = StringArray::fromTokens (bundleIdentifier, ".", StringRef());
1327 
1328                 jassert (bundleIdSegments.size() > 0);
1329                 bundleIdentifier += String (".") + bundleIdSegments[bundleIdSegments.size() - 1] + xcodeBundleIDSubPath;
1330             }
1331 
1332             return bundleIdentifier;
1333         }
1334 
getConfigPreprocessorDefsXcodeTarget1335         StringPairArray getConfigPreprocessorDefs (const XcodeBuildConfiguration& config) const
1336         {
1337             StringPairArray defines;
1338 
1339             if (config.isDebug())
1340             {
1341                 defines.set ("_DEBUG", "1");
1342                 defines.set ("DEBUG", "1");
1343             }
1344             else
1345             {
1346                 defines.set ("_NDEBUG", "1");
1347                 defines.set ("NDEBUG", "1");
1348             }
1349 
1350             if (owner.isInAppPurchasesEnabled())
1351                 defines.set ("JUCE_IN_APP_PURCHASES", "1");
1352 
1353             if (owner.iOS && owner.isContentSharingEnabled())
1354                 defines.set ("JUCE_CONTENT_SHARING", "1");
1355 
1356             if (owner.isPushNotificationsEnabled())
1357                 defines.set ("JUCE_PUSH_NOTIFICATIONS", "1");
1358 
1359             return mergePreprocessorDefs (defines, owner.getAllPreprocessorDefs (config, type));
1360         }
1361 
1362         //==============================================================================
getTargetSettingsXcodeTarget1363         StringPairArray getTargetSettings (const XcodeBuildConfiguration& config) const
1364         {
1365             StringPairArray s;
1366 
1367             if (type == AggregateTarget && ! owner.isiOS())
1368             {
1369                 // the aggregate target needs to have the deployment target set for
1370                 // pre-/post-build scripts
1371                 s.set ("MACOSX_DEPLOYMENT_TARGET", getOSXDeploymentTarget (config.getOSXDeploymentTargetString()));
1372                 s.set ("SDKROOT", getOSXSDKVersion (config.getOSXSDKVersionString()));
1373 
1374                 return s;
1375             }
1376 
1377             s.set ("PRODUCT_NAME", owner.replacePreprocessorTokens (config, config.getTargetBinaryNameString (type == UnityPlugIn)).quoted());
1378             s.set ("PRODUCT_BUNDLE_IDENTIFIER", getBundleIdentifier());
1379 
1380             auto arch = (! owner.isiOS() && type == Target::AudioUnitv3PlugIn) ? osxArch_64Bit
1381                                                                                : config.getOSXArchitectureString();
1382 
1383             if      (arch == osxArch_Native)           s.set ("ARCHS", "\"$(NATIVE_ARCH_ACTUAL)\"");
1384             else if (arch == osxArch_32BitUniversal)   s.set ("ARCHS", "\"$(ARCHS_STANDARD_32_BIT)\"");
1385             else if (arch == osxArch_64BitUniversal)   s.set ("ARCHS", "\"$(ARCHS_STANDARD_32_64_BIT)\"");
1386             else if (arch == osxArch_64Bit)            s.set ("ARCHS", "\"$(ARCHS_STANDARD_64_BIT)\"");
1387 
1388             if (! owner.isiOS())
1389             {
1390                 auto validArchs = owner.getValidArchs();
1391 
1392                 if (! validArchs.isEmpty())
1393                 {
1394                     const auto joined = std::accumulate (validArchs.begin(),
1395                                                          validArchs.end(),
1396                                                          String(),
1397                                                          [] (String str, const var& v) { return str + v.toString() + " "; });
1398 
1399                     s.set ("VALID_ARCHS", joined.trim().quoted());
1400                 }
1401             }
1402 
1403             auto headerPaths = getHeaderSearchPaths (config);
1404 
1405             auto mtlHeaderPaths = headerPaths;
1406 
1407             for (auto& path : mtlHeaderPaths)
1408                 path = path.unquoted();
1409 
1410             s.set ("MTL_HEADER_SEARCH_PATHS", "\"" + mtlHeaderPaths.joinIntoString (" ") + "\"");
1411 
1412             headerPaths.add ("\"$(inherited)\"");
1413             s.set ("HEADER_SEARCH_PATHS", indentParenthesisedList (headerPaths, 1));
1414             s.set ("USE_HEADERMAP", String (static_cast<bool> (config.exporter.settings.getProperty ("useHeaderMap")) ? "YES" : "NO"));
1415 
1416             auto frameworksToSkip = [this]() -> String
1417             {
1418                 const String openGLFramework (owner.iOS ? "OpenGLES" : "OpenGL");
1419 
1420                 if (owner.xcodeFrameworks.contains (openGLFramework))
1421                     return openGLFramework;
1422 
1423                 return {};
1424             }();
1425 
1426             if (frameworksToSkip.isNotEmpty())
1427                 s.set ("VALIDATE_WORKSPACE_SKIPPED_SDK_FRAMEWORKS", frameworksToSkip);
1428 
1429             auto frameworkSearchPaths = getFrameworkSearchPaths (config);
1430 
1431             if (! frameworkSearchPaths.isEmpty())
1432                 s.set ("FRAMEWORK_SEARCH_PATHS", String ("(") + frameworkSearchPaths.joinIntoString (", ") + ", \"$(inherited)\")");
1433 
1434             s.set ("GCC_OPTIMIZATION_LEVEL", config.getGCCOptimisationFlag());
1435 
1436             if (config.shouldUsePrecompiledHeaderFile())
1437             {
1438                 s.set ("GCC_PRECOMPILE_PREFIX_HEADER", "YES");
1439 
1440                 auto pchFileContent = config.getPrecompiledHeaderFileContent();
1441 
1442                 if (pchFileContent.isNotEmpty())
1443                 {
1444                     auto pchFilename = config.getPrecompiledHeaderFilename() + ".h";
1445 
1446                     build_tools::writeStreamToFile (owner.getTargetFolder().getChildFile (pchFilename),
1447                                                     [&] (MemoryOutputStream& mo) { mo << pchFileContent; });
1448 
1449                     s.set ("GCC_PREFIX_HEADER", pchFilename);
1450                 }
1451             }
1452 
1453             if (shouldCreatePList())
1454             {
1455                 s.set ("INFOPLIST_FILE", infoPlistFile.getFileName());
1456 
1457                 if (owner.getPListPrefixHeaderString().isNotEmpty())
1458                     s.set ("INFOPLIST_PREFIX_HEADER", owner.getPListPrefixHeaderString());
1459 
1460                 s.set ("INFOPLIST_PREPROCESS", (owner.isPListPreprocessEnabled() ? String ("YES") : String ("NO")));
1461 
1462                 auto plistDefs = parsePreprocessorDefs (config.getPListPreprocessorDefinitionsString());
1463                 StringArray defsList;
1464 
1465                 for (int i = 0; i < plistDefs.size(); ++i)
1466                 {
1467                     auto def = plistDefs.getAllKeys()[i];
1468                     auto value = plistDefs.getAllValues()[i];
1469 
1470                     if (value.isNotEmpty())
1471                         def << "=" << value.replace ("\"", "\\\\\\\"");
1472 
1473                     defsList.add ("\"" + def + "\"");
1474                 }
1475 
1476                 if (defsList.size() > 0)
1477                     s.set ("INFOPLIST_PREPROCESSOR_DEFINITIONS", indentParenthesisedList (defsList, 1));
1478             }
1479 
1480             if (config.isLinkTimeOptimisationEnabled())
1481                 s.set ("LLVM_LTO", "YES");
1482 
1483             if (config.isFastMathEnabled())
1484                 s.set ("GCC_FAST_MATH", "YES");
1485 
1486             auto flags = (config.getRecommendedCompilerWarningFlags().joinIntoString (" ")
1487                              + " " + owner.getExtraCompilerFlagsString()).trim();
1488             flags = owner.replacePreprocessorTokens (config, flags);
1489 
1490             if (flags.isNotEmpty())
1491                 s.set ("OTHER_CPLUSPLUSFLAGS", flags.quoted());
1492 
1493             auto installPath = getInstallPathForConfiguration (config);
1494 
1495             if (installPath.startsWith ("~"))
1496                 installPath = installPath.replace ("~", "$(HOME)");
1497 
1498             if (installPath.isNotEmpty())
1499             {
1500                 s.set ("INSTALL_PATH", installPath.quoted());
1501 
1502                 if (type == Target::SharedCodeTarget)
1503                     s.set ("SKIP_INSTALL", "YES");
1504 
1505                 if (! owner.embeddedFrameworkIDs.isEmpty())
1506                     s.set ("LD_RUNPATH_SEARCH_PATHS", "\"$(inherited) @executable_path/Frameworks @executable_path/../Frameworks\"");
1507 
1508                 if (xcodeCopyToProductInstallPathAfterBuild)
1509                 {
1510                     s.set ("DEPLOYMENT_LOCATION", "YES");
1511                     s.set ("DSTROOT", "/");
1512                 }
1513             }
1514 
1515             if (getTargetFileType() == pluginBundle)
1516             {
1517                 s.set ("LIBRARY_STYLE", "Bundle");
1518                 s.set ("WRAPPER_EXTENSION", xcodeBundleExtension.substring (1));
1519                 s.set ("GENERATE_PKGINFO_FILE", "YES");
1520             }
1521 
1522             if (xcodeOtherRezFlags.isNotEmpty())
1523                 s.set ("OTHER_REZFLAGS", "\"" + xcodeOtherRezFlags + "\"");
1524 
1525             String configurationBuildDir ("$(PROJECT_DIR)/build/$(CONFIGURATION)");
1526 
1527             if (config.getTargetBinaryRelativePathString().isNotEmpty())
1528             {
1529                 // a target's position can either be defined via installPath + xcodeCopyToProductInstallPathAfterBuild
1530                 // (= for audio plug-ins) or using a custom binary path (for everything else), but not both (= conflict!)
1531                 jassert (! xcodeCopyToProductInstallPathAfterBuild);
1532 
1533                 build_tools::RelativePath binaryPath (config.getTargetBinaryRelativePathString(),
1534                                                       build_tools::RelativePath::projectFolder);
1535 
1536                 configurationBuildDir = expandPath (binaryPath.rebased (owner.projectFolder,
1537                                                                         owner.getTargetFolder(),
1538                                                                         build_tools::RelativePath::buildTargetFolder)
1539                                                               .toUnixStyle());
1540             }
1541 
1542             s.set ("CONFIGURATION_BUILD_DIR", addQuotesIfRequired (configurationBuildDir));
1543 
1544             if (owner.isHardenedRuntimeEnabled())
1545                 s.set ("ENABLE_HARDENED_RUNTIME", "YES");
1546 
1547             String gccVersion ("com.apple.compilers.llvm.clang.1_0");
1548 
1549             if (owner.iOS)
1550             {
1551                 s.set ("ASSETCATALOG_COMPILER_APPICON_NAME", "AppIcon");
1552 
1553                 if (! owner.shouldAddStoryboardToProject())
1554                     s.set ("ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME", "LaunchImage");
1555             }
1556             else
1557             {
1558                 s.set ("MACOSX_DEPLOYMENT_TARGET", getOSXDeploymentTarget (config.getOSXDeploymentTargetString()));
1559             }
1560 
1561             s.set ("GCC_VERSION", gccVersion);
1562             s.set ("CLANG_LINK_OBJC_RUNTIME", "NO");
1563 
1564             auto codeSigningIdentity = owner.getCodeSigningIdentity (config);
1565             s.set (owner.iOS ? "\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\"" : "CODE_SIGN_IDENTITY",
1566                    codeSigningIdentity.quoted());
1567 
1568             if (codeSigningIdentity.isNotEmpty())
1569                 s.set ("PROVISIONING_PROFILE_SPECIFIER", "\"\"");
1570 
1571             if (owner.getDevelopmentTeamIDString().isNotEmpty())
1572                 s.set ("DEVELOPMENT_TEAM", owner.getDevelopmentTeamIDString());
1573 
1574             if (shouldAddEntitlements())
1575                 s.set ("CODE_SIGN_ENTITLEMENTS", getEntitlementsFilename().quoted());
1576 
1577             {
1578                 auto cppStandard = owner.project.getCppStandardString();
1579 
1580                 if (cppStandard == "latest")
1581                     cppStandard = "17";
1582 
1583                 s.set ("CLANG_CXX_LANGUAGE_STANDARD", (String (owner.shouldUseGNUExtensions() ? "gnu++"
1584                                                                                               : "c++") + cppStandard).quoted());
1585             }
1586 
1587             s.set ("CLANG_CXX_LIBRARY", "\"libc++\"");
1588 
1589             s.set ("COMBINE_HIDPI_IMAGES", "YES");
1590 
1591             {
1592                 StringArray linkerFlags, librarySearchPaths;
1593                 getLinkerSettings (config, linkerFlags, librarySearchPaths);
1594 
1595                 if (linkerFlags.size() > 0)
1596                     s.set ("OTHER_LDFLAGS", linkerFlags.joinIntoString (" ").quoted());
1597 
1598                 librarySearchPaths.addArray (config.getLibrarySearchPaths());
1599                 librarySearchPaths = getCleanedStringArray (librarySearchPaths);
1600 
1601                 if (librarySearchPaths.size() > 0)
1602                 {
1603                     StringArray libPaths;
1604                     libPaths.add ("\"$(inherited)\"");
1605 
1606                     for (auto& p : librarySearchPaths)
1607                         libPaths.add ("\"\\\"" + p + "\\\"\"");
1608 
1609                     s.set ("LIBRARY_SEARCH_PATHS", indentParenthesisedList (libPaths, 1));
1610                 }
1611             }
1612 
1613             if (config.isDebug())
1614             {
1615                 s.set ("COPY_PHASE_STRIP", "NO");
1616                 s.set ("GCC_DYNAMIC_NO_PIC", "NO");
1617             }
1618             else
1619             {
1620                 s.set ("GCC_GENERATE_DEBUGGING_SYMBOLS", "NO");
1621                 s.set ("DEAD_CODE_STRIPPING", "YES");
1622             }
1623 
1624             if (type != Target::SharedCodeTarget && type != Target::StaticLibrary && type != Target::DynamicLibrary
1625                 && config.isStripLocalSymbolsEnabled())
1626             {
1627                 s.set ("STRIPFLAGS", "\"-x\"");
1628                 s.set ("DEPLOYMENT_POSTPROCESSING", "YES");
1629                 s.set ("SEPARATE_STRIP", "YES");
1630             }
1631 
1632             StringArray defsList;
1633 
1634             const auto defines = getConfigPreprocessorDefs (config);
1635 
1636             for (int i = 0; i < defines.size(); ++i)
1637             {
1638                 auto def = defines.getAllKeys()[i];
1639                 auto value = defines.getAllValues()[i];
1640                 if (value.isNotEmpty())
1641                     def << "=" << value.replace ("\"", "\\\\\\\"").replace (" ", "\\\\ ");
1642 
1643                 defsList.add ("\"" + def + "\"");
1644             }
1645 
1646             s.set ("GCC_PREPROCESSOR_DEFINITIONS", indentParenthesisedList (defsList, 1));
1647 
1648             StringArray customFlags;
1649             customFlags.addTokens (config.getCustomXcodeFlagsString(), ",", "\"'");
1650             customFlags.removeEmptyStrings();
1651 
1652             for (auto flag : customFlags)
1653             {
1654                 s.set (flag.upToFirstOccurrenceOf ("=", false, false).trim(),
1655                        flag.fromFirstOccurrenceOf ("=", false, false).trim().quoted());
1656             }
1657 
1658             return s;
1659         }
1660 
getInstallPathForConfigurationXcodeTarget1661         String getInstallPathForConfiguration (const XcodeBuildConfiguration& config) const
1662         {
1663             switch (type)
1664             {
1665                 case GUIApp:            return "$(HOME)/Applications";
1666                 case ConsoleApp:        return "/usr/bin";
1667                 case VSTPlugIn:         return config.isPluginBinaryCopyStepEnabled() ? config.getVSTBinaryLocationString() : String();
1668                 case VST3PlugIn:        return config.isPluginBinaryCopyStepEnabled() ? config.getVST3BinaryLocationString() : String();
1669                 case AudioUnitPlugIn:   return config.isPluginBinaryCopyStepEnabled() ? config.getAUBinaryLocationString() : String();
1670                 case RTASPlugIn:        return config.isPluginBinaryCopyStepEnabled() ? config.getRTASBinaryLocationString() : String();
1671                 case AAXPlugIn:         return config.isPluginBinaryCopyStepEnabled() ? config.getAAXBinaryLocationString() : String();
1672                 case UnityPlugIn:       return config.isPluginBinaryCopyStepEnabled() ? config.getUnityPluginBinaryLocationString() : String();
1673                 case SharedCodeTarget:  return owner.isiOS() ? "@executable_path/Frameworks" : "@executable_path/../Frameworks";
1674                 case StaticLibrary:
1675                 case DynamicLibrary:
1676                 case AudioUnitv3PlugIn:
1677                 case StandalonePlugIn:
1678                 case AggregateTarget:
1679                 case unspecified:
1680                 default:                return {};
1681             }
1682         }
1683 
1684         //==============================================================================
getLinkerSettingsXcodeTarget1685         void getLinkerSettings (const BuildConfiguration& config, StringArray& flags, StringArray& librarySearchPaths) const
1686         {
1687             if (getTargetFileType() == pluginBundle)
1688                 flags.add (owner.isiOS() ? "-bitcode_bundle" : "-bundle");
1689 
1690             if (type != Target::SharedCodeTarget)
1691             {
1692                 Array<build_tools::RelativePath> extraLibs;
1693 
1694                 addExtraLibsForTargetType (config, extraLibs);
1695 
1696                 for (auto& lib : extraLibs)
1697                 {
1698                     flags.add (getLinkerFlagForLib (lib.getFileNameWithoutExtension()));
1699                     librarySearchPaths.add (owner.getSearchPathForStaticLibrary (lib));
1700                 }
1701 
1702                 if (owner.project.isAudioPluginProject())
1703                 {
1704                     if (owner.getTargetOfType (Target::SharedCodeTarget) != nullptr)
1705                     {
1706                         auto productName = getStaticLibbedFilename (owner.replacePreprocessorTokens (config, config.getTargetBinaryNameString()));
1707 
1708                         build_tools::RelativePath sharedCodelib (productName, build_tools::RelativePath::buildTargetFolder);
1709                         flags.add (getLinkerFlagForLib (sharedCodelib.getFileNameWithoutExtension()));
1710                     }
1711                 }
1712 
1713                 flags.add (owner.getExternalLibraryFlags (config));
1714 
1715                 auto libs = owner.xcodeLibs;
1716                 libs.addArray (xcodeLibs);
1717 
1718                 for (auto& l : libs)
1719                     flags.add (getLinkerFlagForLib (l));
1720             }
1721 
1722             flags.add (owner.replacePreprocessorTokens (config, owner.getExtraLinkerFlagsString()));
1723             flags = getCleanedStringArray (flags);
1724         }
1725 
1726         //==========================================================================
writeInfoPlistFileXcodeTarget1727         void writeInfoPlistFile() const
1728         {
1729             if (! shouldCreatePList())
1730                 return;
1731 
1732             build_tools::PlistOptions options;
1733 
1734             options.type                             = type;
1735             options.executableName                   = "${EXECUTABLE_NAME}";
1736             options.bundleIdentifier                 = getBundleIdentifier();
1737             options.plistToMerge                     = owner.getPListToMergeString();
1738             options.iOS                              = owner.iOS;
1739             options.microphonePermissionEnabled      = owner.isMicrophonePermissionEnabled();
1740             options.microphonePermissionText         = owner.getMicrophonePermissionsTextString();
1741             options.cameraPermissionEnabled          = owner.isCameraPermissionEnabled();
1742             options.cameraPermissionText             = owner.getCameraPermissionTextString();
1743             options.bluetoothPermissionEnabled       = owner.isBluetoothPermissionEnabled();
1744             options.bluetoothPermissionText          = owner.getBluetoothPermissionTextString();
1745             options.sendAppleEventsPermissionEnabled = owner.isSendAppleEventsPermissionEnabled();
1746             options.sendAppleEventsPermissionText    = owner.getSendAppleEventsPermissionTextString();
1747             options.shouldAddStoryboardToProject     = owner.shouldAddStoryboardToProject();
1748             options.iconFile                         = owner.iconFile;
1749             options.projectName                      = owner.projectName;
1750             options.version                          = owner.project.getVersionString();
1751             options.companyCopyright                 = owner.project.getCompanyCopyrightString();
1752             options.allPreprocessorDefs              = owner.getAllPreprocessorDefs();
1753             options.documentExtensions               = owner.getDocumentExtensionsString();
1754             options.fileSharingEnabled               = owner.isFileSharingEnabled();
1755             options.documentBrowserEnabled           = owner.isDocumentBrowserEnabled();
1756             options.statusBarHidden                  = owner.isStatusBarHidden();
1757             options.requiresFullScreen               = owner.requiresFullScreen();
1758             options.backgroundAudioEnabled           = owner.isBackgroundAudioEnabled();
1759             options.backgroundBleEnabled             = owner.isBackgroundBleEnabled();
1760             options.pushNotificationsEnabled         = owner.isPushNotificationsEnabled();
1761             options.enableIAA                        = owner.project.shouldEnableIAA();
1762             options.IAAPluginName                    = owner.project.getIAAPluginName();
1763             options.pluginManufacturerCode           = owner.project.getPluginManufacturerCodeString();
1764             options.IAATypeCode                      = owner.project.getIAATypeCode();
1765             options.pluginCode                       = owner.project.getPluginCodeString();
1766             options.versionAsHex                     = owner.project.getVersionAsHexInteger();
1767             options.iPhoneScreenOrientations         = owner.getiPhoneScreenOrientations();
1768             options.iPadScreenOrientations           = owner.getiPadScreenOrientations();
1769 
1770             options.storyboardName = [&]
1771             {
1772                 const auto customLaunchStoryboard = owner.getCustomLaunchStoryboardString();
1773 
1774                 if (customLaunchStoryboard.isEmpty())
1775                     return owner.getDefaultLaunchStoryboardName();
1776 
1777                 return customLaunchStoryboard.fromLastOccurrenceOf ("/", false, false)
1778                                              .upToLastOccurrenceOf (".storyboard", false, false);
1779             }();
1780 
1781             options.pluginName                      = owner.project.getPluginNameString();
1782             options.pluginManufacturer              = owner.project.getPluginManufacturerString();
1783             options.pluginDescription               = owner.project.getPluginDescriptionString();
1784             options.pluginAUExportPrefix            = owner.project.getPluginAUExportPrefixString();
1785             options.auMainType                      = owner.project.getAUMainTypeString();
1786             options.isAuSandboxSafe                 = owner.project.isAUSandBoxSafe();
1787             options.isPluginSynth                   = owner.project.isPluginSynth();
1788             options.suppressResourceUsage           = owner.getSuppressPlistResourceUsage();
1789 
1790             options.write (infoPlistFile);
1791         }
1792 
1793         //==============================================================================
addShellScriptBuildPhaseXcodeTarget1794         void addShellScriptBuildPhase (const String& phaseName, const String& script)
1795         {
1796             if (script.trim().isNotEmpty())
1797             {
1798                 auto v = addBuildPhase ("PBXShellScriptBuildPhase", {});
1799                 v.setProperty (Ids::name, phaseName, nullptr);
1800                 v.setProperty ("shellPath", "/bin/sh", nullptr);
1801                 v.setProperty ("shellScript", script.replace ("\\", "\\\\")
1802                                                     .replace ("\"", "\\\"")
1803                                                     .replace ("\r\n", "\\n")
1804                                                     .replace ("\n", "\\n"), nullptr);
1805             }
1806         }
1807 
addCopyFilesPhaseXcodeTarget1808         void addCopyFilesPhase (const String& phaseName, const StringArray& files, XcodeCopyFilesDestinationIDs dst)
1809         {
1810             auto v = addBuildPhase ("PBXCopyFilesBuildPhase", files, phaseName);
1811             v.setProperty ("dstPath", "", nullptr);
1812             v.setProperty ("dstSubfolderSpec", (int) dst, nullptr);
1813         }
1814 
1815         //==============================================================================
sanitiseAndEscapeSearchPathsXcodeTarget1816         void sanitiseAndEscapeSearchPaths (const BuildConfiguration& config, StringArray& paths) const
1817         {
1818             paths = getCleanedStringArray (paths);
1819 
1820             for (auto& path : paths)
1821             {
1822                 path = owner.replacePreprocessorTokens (config, expandPath (path));
1823 
1824                 if (path.containsChar (' '))
1825                     path = "\"\\\"" + path + "\\\"\""; // crazy double quotes required when there are spaces..
1826                 else
1827                     path = "\"" + path + "\"";
1828             }
1829         }
1830 
getHeaderSearchPathsXcodeTarget1831         StringArray getHeaderSearchPaths (const BuildConfiguration& config) const
1832         {
1833             StringArray paths (owner.extraSearchPaths);
1834             paths.addArray (config.getHeaderSearchPaths());
1835             paths.addArray (getTargetExtraHeaderSearchPaths());
1836 
1837             if (owner.project.getEnabledModules().isModuleEnabled ("juce_audio_plugin_client"))
1838             {
1839                 // Needed to compile .r files
1840                 paths.add (owner.getModuleFolderRelativeToProject ("juce_audio_plugin_client")
1841                                 .rebased (owner.projectFolder, owner.getTargetFolder(), build_tools::RelativePath::buildTargetFolder)
1842                                 .toUnixStyle());
1843             }
1844 
1845             sanitiseAndEscapeSearchPaths (config, paths);
1846             return paths;
1847         }
1848 
getFrameworkSearchPathsXcodeTarget1849         StringArray getFrameworkSearchPaths (const BuildConfiguration& config) const
1850         {
1851             auto paths = getSearchPathsFromString (owner.getFrameworkSearchPathsString());
1852             sanitiseAndEscapeSearchPaths (config, paths);
1853             return paths;
1854         }
1855 
1856     private:
1857         //==============================================================================
addExtraAudioUnitTargetSettingsXcodeTarget1858         void addExtraAudioUnitTargetSettings()
1859         {
1860             xcodeOtherRezFlags = "-d ppc_$ppc -d i386_$i386 -d ppc64_$ppc64 -d x86_64_$x86_64"
1861                                  " -I /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Versions/A/Headers"
1862                                  " -I \\\"$(DEVELOPER_DIR)/Extras/CoreAudio/AudioUnits/AUPublic/AUBase\\\""
1863                                  " -I \\\"$(DEVELOPER_DIR)/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/System/Library/Frameworks/AudioUnit.framework/Headers\\\"";
1864 
1865             xcodeFrameworks.addArray ({ "AudioUnit", "CoreAudioKit" });
1866         }
1867 
addExtraAudioUnitv3PlugInTargetSettingsXcodeTarget1868         void addExtraAudioUnitv3PlugInTargetSettings()
1869         {
1870             xcodeFrameworks.addArray ({ "AVFoundation", "CoreAudioKit" });
1871 
1872             if (owner.isOSX())
1873                 xcodeFrameworks.add ("AudioUnit");
1874         }
1875 
addExtraLibsForTargetTypeXcodeTarget1876         void addExtraLibsForTargetType  (const BuildConfiguration& config, Array<build_tools::RelativePath>& extraLibs) const
1877         {
1878             if (type == AAXPlugIn)
1879             {
1880                 auto aaxLibsFolder = build_tools::RelativePath (owner.getAAXPathString(), build_tools::RelativePath::projectFolder).getChildFile ("Libs");
1881 
1882                 String libraryPath (config.isDebug() ? "Debug" : "Release");
1883                 libraryPath += "/libAAXLibrary_libcpp.a";
1884 
1885                 extraLibs.add   (aaxLibsFolder.getChildFile (libraryPath));
1886             }
1887             else if (type == RTASPlugIn)
1888             {
1889                 build_tools::RelativePath rtasFolder (owner.getRTASPathString(), build_tools::RelativePath::projectFolder);
1890 
1891                 extraLibs.add (rtasFolder.getChildFile ("MacBag/Libs/Debug/libPluginLibrary.a"));
1892                 extraLibs.add (rtasFolder.getChildFile ("MacBag/Libs/Release/libPluginLibrary.a"));
1893             }
1894         }
1895 
getTargetExtraHeaderSearchPathsXcodeTarget1896         StringArray getTargetExtraHeaderSearchPaths() const
1897         {
1898             StringArray targetExtraSearchPaths;
1899 
1900             if (type == RTASPlugIn)
1901             {
1902                 build_tools::RelativePath rtasFolder (owner.getRTASPathString(), build_tools::RelativePath::projectFolder);
1903 
1904                 targetExtraSearchPaths.add ("$(DEVELOPER_DIR)/Headers/FlatCarbon");
1905                 targetExtraSearchPaths.add ("$(SDKROOT)/Developer/Headers/FlatCarbon");
1906 
1907                 static const char* p[] = { "AlturaPorts/TDMPlugIns/PlugInLibrary/Controls",
1908                     "AlturaPorts/TDMPlugIns/PlugInLibrary/CoreClasses",
1909                     "AlturaPorts/TDMPlugIns/PlugInLibrary/DSPClasses",
1910                     "AlturaPorts/TDMPlugIns/PlugInLibrary/EffectClasses",
1911                     "AlturaPorts/TDMPlugIns/PlugInLibrary/MacBuild",
1912                     "AlturaPorts/TDMPlugIns/PlugInLibrary/Meters",
1913                     "AlturaPorts/TDMPlugIns/PlugInLibrary/ProcessClasses",
1914                     "AlturaPorts/TDMPlugIns/PlugInLibrary/ProcessClasses/Interfaces",
1915                     "AlturaPorts/TDMPlugIns/PlugInLibrary/RTASP_Adapt",
1916                     "AlturaPorts/TDMPlugIns/PlugInLibrary/Utilities",
1917                     "AlturaPorts/TDMPlugIns/PlugInLibrary/ViewClasses",
1918                     "AlturaPorts/TDMPlugIns/DSPManager/**",
1919                     "AlturaPorts/TDMPlugIns/SupplementalPlugInLib/Encryption",
1920                     "AlturaPorts/TDMPlugIns/SupplementalPlugInLib/GraphicsExtensions",
1921                     "AlturaPorts/TDMPlugIns/common/**",
1922                     "AlturaPorts/TDMPlugIns/common/PI_LibInterface",
1923                     "AlturaPorts/TDMPlugIns/PACEProtection/**",
1924                     "AlturaPorts/TDMPlugIns/SignalProcessing/**",
1925                     "AlturaPorts/OMS/Headers",
1926                     "AlturaPorts/Fic/Interfaces/**",
1927                     "AlturaPorts/Fic/Source/SignalNets",
1928                     "AlturaPorts/DSIPublicInterface/PublicHeaders",
1929                     "DAEWin/Include",
1930                     "AlturaPorts/DigiPublic/Interfaces",
1931                     "AlturaPorts/DigiPublic",
1932                     "AlturaPorts/NewFileLibs/DOA",
1933                     "AlturaPorts/NewFileLibs/Cmn",
1934                     "xplat/AVX/avx2/avx2sdk/inc",
1935                     "xplat/AVX/avx2/avx2sdk/utils" };
1936 
1937                 for (auto* path : p)
1938                     owner.addProjectPathToBuildPathList (targetExtraSearchPaths, rtasFolder.getChildFile (path));
1939             }
1940 
1941             return targetExtraSearchPaths;
1942         }
1943 
getOSXDeploymentTargetXcodeTarget1944         String getOSXDeploymentTarget (const String& deploymentTarget) const
1945         {
1946             auto minVersion = (type == Target::AudioUnitv3PlugIn ? minimumAUv3SDKVersion
1947                                                                  : oldestDeploymentTarget);
1948 
1949             for (auto v = minVersion; v != nextMacOSVersion; ++v)
1950                 if (deploymentTarget == getDisplayName (v))
1951                     return ::getName (v);
1952 
1953             return ::getName (minVersion);
1954         }
1955 
1956         //==============================================================================
1957         const XcodeProjectExporter& owner;
1958 
1959         Target& operator= (const Target&) = delete;
1960     };
1961 
1962     mutable StringArray xcodeFrameworks;
1963     StringArray xcodeLibs;
1964 
1965 private:
1966     //==============================================================================
expandPath(const String & path)1967     static String expandPath (const String& path)
1968     {
1969         if (! File::isAbsolutePath (path))  return "$(SRCROOT)/" + path;
1970         if (path.startsWithChar ('~'))      return "$(HOME)" + path.substring (1);
1971 
1972         return path;
1973     }
1974 
addQuotesIfRequired(const String & s)1975     static String addQuotesIfRequired (const String& s)
1976     {
1977         return s.containsAnyOf (" $") ? s.quoted() : s;
1978     }
1979 
getProjectBundle()1980     File getProjectBundle() const                 { return getTargetFolder().getChildFile (project.getProjectFilenameRootString()).withFileExtension (".xcodeproj"); }
1981 
1982     //==============================================================================
createObjects()1983     void createObjects() const
1984     {
1985         prepareTargets();
1986 
1987         // Must be called before adding embedded frameworks, as we want to
1988         // embed any frameworks found in subprojects.
1989         addSubprojects();
1990 
1991         addFrameworks();
1992         addCustomFrameworks();
1993         addEmbeddedFrameworks();
1994 
1995         addCustomResourceFolders();
1996         addPlistFileReferences();
1997 
1998         if (iOS && ! projectType.isStaticLibrary())
1999         {
2000             addXcassets();
2001 
2002             if (shouldAddStoryboardToProject())
2003             {
2004                 auto customLaunchStoryboard = getCustomLaunchStoryboardString();
2005 
2006                 if (customLaunchStoryboard.isEmpty())
2007                     writeDefaultLaunchStoryboardFile();
2008                 else if (getProject().getProjectFolder().getChildFile (customLaunchStoryboard).existsAsFile())
2009                     addLaunchStoryboardFileReference (build_tools::RelativePath (customLaunchStoryboard, build_tools::RelativePath::projectFolder)
2010                                                           .rebased (getProject().getProjectFolder(), getTargetFolder(), build_tools::RelativePath::buildTargetFolder));
2011             }
2012         }
2013         else
2014         {
2015             addNibFiles();
2016         }
2017 
2018         addIcons();
2019         addBuildConfigurations();
2020 
2021         addProjectConfigList (createID ("__projList"));
2022 
2023         {
2024             StringArray topLevelGroupIDs;
2025 
2026             addFilesAndGroupsToProject (topLevelGroupIDs);
2027             addBuildPhases();
2028             addExtraGroupsToProject (topLevelGroupIDs);
2029 
2030             addGroup (createID ("__mainsourcegroup"), "Source", topLevelGroupIDs);
2031         }
2032 
2033         addProjectObject();
2034         removeMismatchedXcuserdata();
2035     }
2036 
prepareTargets()2037     void prepareTargets() const
2038     {
2039         for (auto* target : targets)
2040         {
2041             target->addDependencies();
2042 
2043             if (target->type == XcodeTarget::AggregateTarget)
2044                 continue;
2045 
2046             target->addMainBuildProduct();
2047 
2048             auto targetName = String (target->getName());
2049             auto fileID = createID (targetName + "__targetbuildref");
2050             auto fileRefID = createID ("__productFileID" + targetName);
2051 
2052             ValueTree v (fileID + " /* " + targetName + " */");
2053             v.setProperty ("isa", "PBXBuildFile", nullptr);
2054             v.setProperty ("fileRef", fileRefID, nullptr);
2055 
2056             target->mainBuildProductID = fileID;
2057 
2058             addObject (v);
2059         }
2060     }
2061 
addPlistFileReferences()2062     void addPlistFileReferences() const
2063     {
2064         for (auto* target : targets)
2065         {
2066             if (target->type == XcodeTarget::AggregateTarget)
2067                 continue;
2068 
2069             if (target->shouldCreatePList())
2070             {
2071                 build_tools::RelativePath plistPath (target->infoPlistFile, getTargetFolder(), build_tools::RelativePath::buildTargetFolder);
2072                 addFileReference (plistPath.toUnixStyle());
2073                 resourceFileRefs.add (createFileRefID (plistPath));
2074             }
2075         }
2076     }
2077 
addNibFiles()2078     void addNibFiles() const
2079     {
2080         build_tools::writeStreamToFile (menuNibFile, [&] (MemoryOutputStream& mo)
2081         {
2082             mo.write (BinaryData::RecentFilesMenuTemplate_nib, BinaryData::RecentFilesMenuTemplate_nibSize);
2083         });
2084 
2085         build_tools::RelativePath menuNibPath (menuNibFile, getTargetFolder(), build_tools::RelativePath::buildTargetFolder);
2086         addFileReference (menuNibPath.toUnixStyle());
2087         resourceIDs.add (addBuildFile (FileOptions().withRelativePath (menuNibPath)));
2088         resourceFileRefs.add (createFileRefID (menuNibPath));
2089     }
2090 
addIcons()2091     void addIcons() const
2092     {
2093         if (iconFile.exists())
2094         {
2095             build_tools::RelativePath iconPath (iconFile, getTargetFolder(), build_tools::RelativePath::buildTargetFolder);
2096             addFileReference (iconPath.toUnixStyle());
2097             resourceIDs.add (addBuildFile (FileOptions().withRelativePath (iconPath)));
2098             resourceFileRefs.add (createFileRefID (iconPath));
2099         }
2100     }
2101 
addBuildConfigurations()2102     void addBuildConfigurations() const
2103     {
2104         for (ConstConfigIterator config (*this); config.next();)
2105         {
2106             auto& xcodeConfig = dynamic_cast<const XcodeBuildConfiguration&> (*config);
2107             StringArray settingsLines;
2108             auto configSettings = getProjectSettings (xcodeConfig);
2109             auto keys = configSettings.getAllKeys();
2110             keys.sort (false);
2111 
2112             for (auto& key : keys)
2113                 settingsLines.add (key + " = " + configSettings[key]);
2114 
2115             addProjectConfig (config->getName(), settingsLines);
2116         }
2117     }
2118 
addFilesAndGroupsToProject(StringArray & topLevelGroupIDs)2119     void addFilesAndGroupsToProject (StringArray& topLevelGroupIDs) const
2120     {
2121         for (auto* target : targets)
2122             if (target->shouldAddEntitlements())
2123                 addEntitlementsFile (*target);
2124 
2125         for (auto& group : getAllGroups())
2126         {
2127             if (group.getNumChildren() > 0)
2128             {
2129                 auto groupID = addProjectItem (group);
2130 
2131                 if (groupID.isNotEmpty())
2132                     topLevelGroupIDs.add (groupID);
2133             }
2134         }
2135     }
2136 
addExtraGroupsToProject(StringArray & topLevelGroupIDs)2137     void addExtraGroupsToProject (StringArray& topLevelGroupIDs) const
2138     {
2139         {
2140             auto resourcesGroupID = createID ("__resources");
2141             addGroup (resourcesGroupID, "Resources", resourceFileRefs);
2142             topLevelGroupIDs.add (resourcesGroupID);
2143         }
2144 
2145         {
2146             auto frameworksGroupID = createID ("__frameworks");
2147             addGroup (frameworksGroupID, "Frameworks", frameworkFileIDs);
2148             topLevelGroupIDs.add (frameworksGroupID);
2149         }
2150 
2151         {
2152             auto productsGroupID = createID ("__products");
2153             addGroup (productsGroupID, "Products", buildProducts);
2154             topLevelGroupIDs.add (productsGroupID);
2155         }
2156 
2157         if (! subprojectFileIDs.isEmpty())
2158         {
2159             auto subprojectLibrariesGroupID = createID ("__subprojects");
2160             addGroup (subprojectLibrariesGroupID, "Subprojects", subprojectFileIDs);
2161             topLevelGroupIDs.add (subprojectLibrariesGroupID);
2162         }
2163     }
2164 
addBuildPhases()2165     void addBuildPhases() const
2166     {
2167         // add build phases
2168         for (auto* target : targets)
2169         {
2170             if (target->type != XcodeTarget::AggregateTarget)
2171                 buildProducts.add (createID (String ("__productFileID") + String (target->getName())));
2172 
2173             for (ConstConfigIterator config (*this); config.next();)
2174             {
2175                 auto& xcodeConfig = dynamic_cast<const XcodeBuildConfiguration&> (*config);
2176 
2177                 auto configSettings = target->getTargetSettings (xcodeConfig);
2178                 StringArray settingsLines;
2179                 auto keys = configSettings.getAllKeys();
2180                 keys.sort (false);
2181 
2182                 for (auto& key : keys)
2183                     settingsLines.add (key + " = " + configSettings.getValue (key, "\"\""));
2184 
2185                 target->addTargetConfig (config->getName(), settingsLines);
2186             }
2187 
2188             addConfigList (*target, createID (String ("__configList") + target->getName()));
2189 
2190             target->addShellScriptBuildPhase ("Pre-build script", getPreBuildScript());
2191 
2192             if (target->type != XcodeTarget::AggregateTarget)
2193             {
2194                 auto skipAUv3 = (target->type == XcodeTarget::AudioUnitv3PlugIn && ! shouldDuplicateAppExResourcesFolder());
2195 
2196                 if (! projectType.isStaticLibrary() && target->type != XcodeTarget::SharedCodeTarget && ! skipAUv3)
2197                     target->addBuildPhase ("PBXResourcesBuildPhase", resourceIDs);
2198 
2199                 auto rezFiles = rezFileIDs;
2200                 rezFiles.addArray (target->rezFileIDs);
2201 
2202                 if (rezFiles.size() > 0)
2203                     target->addBuildPhase ("PBXRezBuildPhase", rezFiles);
2204 
2205                 auto sourceFiles = target->sourceIDs;
2206 
2207                 if (target->type == XcodeTarget::SharedCodeTarget
2208                      || (! project.isAudioPluginProject()))
2209                     sourceFiles.addArray (sourceIDs);
2210 
2211                 target->addBuildPhase ("PBXSourcesBuildPhase", sourceFiles);
2212 
2213                 if (! projectType.isStaticLibrary() && target->type != XcodeTarget::SharedCodeTarget)
2214                     target->addBuildPhase ("PBXFrameworksBuildPhase", target->frameworkIDs);
2215             }
2216 
2217             target->addShellScriptBuildPhase ("Post-build script", getPostBuildScript());
2218 
2219             if (project.isAudioPluginProject() && project.shouldBuildAUv3()
2220                 && project.shouldBuildStandalonePlugin() && target->type == XcodeTarget::StandalonePlugIn)
2221                 embedAppExtension();
2222 
2223             if (project.isAudioPluginProject() && project.shouldBuildUnityPlugin()
2224                 && target->type == XcodeTarget::UnityPlugIn)
2225                 embedUnityScript();
2226 
2227             addTargetObject (*target);
2228         }
2229     }
2230 
embedAppExtension()2231     void embedAppExtension() const
2232     {
2233         if (auto* standaloneTarget = getTargetOfType (XcodeTarget::StandalonePlugIn))
2234         {
2235             if (auto* auv3Target   = getTargetOfType (XcodeTarget::AudioUnitv3PlugIn))
2236             {
2237                 StringArray files;
2238                 files.add (auv3Target->mainBuildProductID);
2239                 standaloneTarget->addCopyFilesPhase ("Embed App Extensions", files, kPluginsFolder);
2240             }
2241         }
2242     }
2243 
embedUnityScript()2244     void embedUnityScript() const
2245     {
2246         if (auto* unityTarget = getTargetOfType (XcodeTarget::UnityPlugIn))
2247         {
2248             build_tools::RelativePath scriptPath (getProject().getGeneratedCodeFolder().getChildFile (getProject().getUnityScriptName()),
2249                                                   getTargetFolder(),
2250                                                   build_tools::RelativePath::buildTargetFolder);
2251 
2252             auto path = scriptPath.toUnixStyle();
2253             auto refID = addFileReference (path);
2254             auto fileID = addBuildFile (FileOptions().withPath (path)
2255                                                      .withFileRefID (refID));
2256 
2257             resourceIDs.add (fileID);
2258             resourceFileRefs.add (refID);
2259 
2260             unityTarget->addCopyFilesPhase ("Embed Unity Script", fileID, kResourcesFolder);
2261         }
2262     }
2263 
2264     //==============================================================================
getTargetOfType(build_tools::ProjectType::Target::Type type)2265     XcodeTarget* getTargetOfType (build_tools::ProjectType::Target::Type type) const
2266     {
2267         for (auto& target : targets)
2268             if (target->type == type)
2269                 return target;
2270 
2271         return nullptr;
2272     }
2273 
addTargetObject(XcodeTarget & target)2274     void addTargetObject (XcodeTarget& target) const
2275     {
2276         auto targetName = target.getName();
2277 
2278         auto targetID = target.getID();
2279         ValueTree v (targetID);
2280         v.setProperty ("isa", target.type == XcodeTarget::AggregateTarget ? "PBXAggregateTarget" : "PBXNativeTarget", nullptr);
2281         v.setProperty ("buildConfigurationList", createID (String ("__configList") + targetName), nullptr);
2282 
2283         v.setProperty ("buildPhases", indentParenthesisedList (target.buildPhaseIDs), nullptr);
2284 
2285         if (target.type != XcodeTarget::AggregateTarget)
2286             v.setProperty ("buildRules", indentParenthesisedList ({}), nullptr);
2287 
2288         StringArray allDependencyIDs { subprojectDependencyIDs };
2289         allDependencyIDs.addArray (target.dependencyIDs);
2290         v.setProperty ("dependencies", indentParenthesisedList (allDependencyIDs), nullptr);
2291 
2292         v.setProperty (Ids::name, target.getXcodeSchemeName(), nullptr);
2293         v.setProperty ("productName", projectName, nullptr);
2294 
2295         if (target.type != XcodeTarget::AggregateTarget)
2296         {
2297             v.setProperty ("productReference", createID (String ("__productFileID") + targetName), nullptr);
2298 
2299             jassert (target.xcodeProductType.isNotEmpty());
2300             v.setProperty ("productType", target.xcodeProductType, nullptr);
2301         }
2302 
2303         targetIDs.add (targetID);
2304         addObject (v);
2305     }
2306 
createIconFile()2307     void createIconFile() const
2308     {
2309         const auto icons = getIcons();
2310 
2311         if (! build_tools::asArray (icons).isEmpty())
2312         {
2313             iconFile = getTargetFolder().getChildFile ("Icon.icns");
2314             build_tools::writeMacIcon (icons, iconFile);
2315         }
2316     }
2317 
writeWorkspaceSettings()2318     void writeWorkspaceSettings() const
2319     {
2320         const auto settingsFile = getProjectBundle().getChildFile ("project.xcworkspace")
2321                                                     .getChildFile ("xcshareddata")
2322                                                     .getChildFile ("WorkspaceSettings.xcsettings");
2323 
2324         if (shouldUseLegacyBuildSystem())
2325         {
2326             build_tools::writeStreamToFile (settingsFile, [this] (MemoryOutputStream& mo)
2327             {
2328                 mo.setNewLineString (getNewLineString());
2329 
2330                 mo << "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"              << newLine
2331                    << "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">" << newLine
2332                    << "<plist version=\"1.0\">"                                 << newLine
2333                    << "<dict>"                                                  << newLine
2334                    << "\t" << "<key>BuildSystemType</key>"                      << newLine
2335                    << "\t" << "<string>Original</string>"                       << newLine
2336                    << "\t" << "<key>DisableBuildSystemDeprecationWarning</key>" << newLine
2337                    << "\t" << "<true/>"                                         << newLine
2338                    << "</dict>"                                                 << newLine
2339                    << "</plist>"                                                << newLine;
2340             });
2341         }
2342         else
2343         {
2344             settingsFile.deleteFile();
2345         }
2346     }
2347 
writeInfoPlistFiles()2348     void writeInfoPlistFiles() const
2349     {
2350         for (auto& target : targets)
2351            target->writeInfoPlistFile();
2352     }
2353 
2354     // Delete .rsrc files in folder but don't follow sym-links
deleteRsrcFiles(const File & folder)2355     void deleteRsrcFiles (const File& folder) const
2356     {
2357         for (const auto& di : RangedDirectoryIterator (folder, false, "*", File::findFilesAndDirectories))
2358         {
2359             const auto& entry = di.getFile();
2360 
2361             if (! entry.isSymbolicLink())
2362             {
2363                 if (entry.existsAsFile() && entry.getFileExtension().toLowerCase() == ".rsrc")
2364                     entry.deleteFile();
2365                 else if (entry.isDirectory())
2366                     deleteRsrcFiles (entry);
2367             }
2368         }
2369     }
2370 
getLinkerFlagForLib(String library)2371     static String getLinkerFlagForLib (String library)
2372     {
2373         if (library.substring (0, 3) == "lib")
2374             library = library.substring (3);
2375 
2376         return "-l" + library.replace (" ", "\\\\ ").replace ("\"", "\\\\\"").replace ("\'", "\\\\\'").upToLastOccurrenceOf (".", false, false);
2377     }
2378 
getSearchPathForStaticLibrary(const build_tools::RelativePath & library)2379     String getSearchPathForStaticLibrary (const build_tools::RelativePath& library) const
2380     {
2381         auto searchPath = library.toUnixStyle().upToLastOccurrenceOf ("/", false, false);
2382 
2383         if (! library.isAbsolute())
2384         {
2385             auto srcRoot = rebaseFromProjectFolderToBuildTarget (build_tools::RelativePath (".", build_tools::RelativePath::projectFolder)).toUnixStyle();
2386 
2387             if (srcRoot.endsWith ("/."))      srcRoot = srcRoot.dropLastCharacters (2);
2388             if (! srcRoot.endsWithChar ('/')) srcRoot << '/';
2389 
2390             searchPath = srcRoot + searchPath;
2391         }
2392 
2393         return expandPath (searchPath);
2394     }
2395 
getCodeSigningIdentity(const XcodeBuildConfiguration & config)2396     String getCodeSigningIdentity (const XcodeBuildConfiguration& config) const
2397     {
2398         auto identity = config.getCodeSignIdentityString();
2399 
2400         if (identity.isEmpty() && getDevelopmentTeamIDString().isNotEmpty())
2401             return iOS ? "iPhone Developer" : "Mac Developer";
2402 
2403         return identity;
2404     }
2405 
getProjectSettings(const XcodeBuildConfiguration & config)2406     StringPairArray getProjectSettings (const XcodeBuildConfiguration& config) const
2407     {
2408         StringPairArray s;
2409 
2410         s.set ("ALWAYS_SEARCH_USER_PATHS", "NO");
2411         s.set ("ENABLE_STRICT_OBJC_MSGSEND", "YES");
2412         s.set ("GCC_C_LANGUAGE_STANDARD", "c11");
2413         s.set ("GCC_NO_COMMON_BLOCKS", "YES");
2414         s.set ("GCC_MODEL_TUNING", "G5");
2415         s.set ("GCC_WARN_ABOUT_RETURN_TYPE", "YES");
2416         s.set ("GCC_WARN_CHECK_SWITCH_STATEMENTS", "YES");
2417         s.set ("GCC_WARN_UNUSED_VARIABLE", "YES");
2418         s.set ("GCC_WARN_MISSING_PARENTHESES", "YES");
2419         s.set ("GCC_WARN_NON_VIRTUAL_DESTRUCTOR", "YES");
2420         s.set ("GCC_WARN_TYPECHECK_CALLS_TO_PRINTF", "YES");
2421         s.set ("GCC_WARN_64_TO_32_BIT_CONVERSION", "YES");
2422         s.set ("GCC_WARN_UNDECLARED_SELECTOR", "YES");
2423         s.set ("GCC_WARN_UNINITIALIZED_AUTOS", "YES");
2424         s.set ("GCC_WARN_UNUSED_FUNCTION", "YES");
2425         s.set ("CLANG_ENABLE_OBJC_WEAK", "YES");
2426         s.set ("CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING", "YES");
2427         s.set ("CLANG_WARN_BOOL_CONVERSION", "YES");
2428         s.set ("CLANG_WARN_COMMA", "YES");
2429         s.set ("CLANG_WARN_CONSTANT_CONVERSION", "YES");
2430         s.set ("CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS", "YES");
2431         s.set ("CLANG_WARN_EMPTY_BODY", "YES");
2432         s.set ("CLANG_WARN_ENUM_CONVERSION", "YES");
2433         s.set ("CLANG_WARN_INFINITE_RECURSION", "YES");
2434         s.set ("CLANG_WARN_INT_CONVERSION", "YES");
2435         s.set ("CLANG_WARN_NON_LITERAL_NULL_CONVERSION", "YES");
2436         s.set ("CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF", "YES");
2437         s.set ("CLANG_WARN_OBJC_LITERAL_CONVERSION", "YES");
2438         s.set ("CLANG_WARN_RANGE_LOOP_ANALYSIS", "YES");
2439         s.set ("CLANG_WARN_STRICT_PROTOTYPES", "YES");
2440         s.set ("CLANG_WARN_SUSPICIOUS_MOVE", "YES");
2441         s.set ("CLANG_WARN_UNREACHABLE_CODE", "YES");
2442         s.set ("CLANG_WARN__DUPLICATE_METHOD_MATCH", "YES");
2443         s.set ("WARNING_CFLAGS", "\"-Wreorder\"");
2444         s.set ("GCC_INLINES_ARE_PRIVATE_EXTERN", projectType.isStaticLibrary() ? "NO" : "YES");
2445 
2446         // GCC_SYMBOLS_PRIVATE_EXTERN only takes effect if ENABLE_TESTABILITY is off
2447         s.set ("ENABLE_TESTABILITY", "NO");
2448         s.set ("GCC_SYMBOLS_PRIVATE_EXTERN", "YES");
2449 
2450         if (config.isDebug())
2451         {
2452             if (config.getOSXArchitectureString() == osxArch_Default)
2453                 s.set ("ONLY_ACTIVE_ARCH", "YES");
2454         }
2455 
2456         s.set (iOS ? "\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\"" : "CODE_SIGN_IDENTITY",
2457                getCodeSigningIdentity (config).quoted());
2458 
2459         if (iOS)
2460         {
2461             s.set ("SDKROOT", "iphoneos");
2462             s.set ("TARGETED_DEVICE_FAMILY", getDeviceFamilyString().quoted());
2463             s.set ("IPHONEOS_DEPLOYMENT_TARGET", config.getiOSDeploymentTargetString());
2464         }
2465         else
2466         {
2467             s.set ("SDKROOT", getOSXSDKVersion (config.getOSXSDKVersionString()));
2468         }
2469 
2470         s.set ("ZERO_LINK", "NO");
2471 
2472         if (xcodeCanUseDwarf)
2473             s.set ("DEBUG_INFORMATION_FORMAT", "dwarf");
2474 
2475         s.set ("PRODUCT_NAME", replacePreprocessorTokens (config, config.getTargetBinaryNameString()).quoted());
2476 
2477         return s;
2478     }
2479 
addFrameworks()2480     void addFrameworks() const
2481     {
2482         if (! projectType.isStaticLibrary())
2483         {
2484             if (isInAppPurchasesEnabled())
2485                 xcodeFrameworks.addIfNotAlreadyThere ("StoreKit");
2486 
2487             if (iOS && isPushNotificationsEnabled())
2488                 xcodeFrameworks.addIfNotAlreadyThere ("UserNotifications");
2489 
2490             if (iOS
2491                 && project.getEnabledModules().isModuleEnabled ("juce_video")
2492                 && project.isConfigFlagEnabled ("JUCE_USE_CAMERA", false))
2493             {
2494                 xcodeFrameworks.addIfNotAlreadyThere ("ImageIO");
2495             }
2496 
2497             xcodeFrameworks.addTokens (getExtraFrameworksString(), ",;", "\"'");
2498             xcodeFrameworks.trim();
2499 
2500             auto s = xcodeFrameworks;
2501 
2502             for (auto& target : targets)
2503                 s.addArray (target->xcodeFrameworks);
2504 
2505             if (! project.getConfigFlag ("JUCE_QUICKTIME").get())
2506                 s.removeString ("QuickTime");
2507 
2508             s.trim();
2509             s.removeDuplicates (true);
2510             s.sort (true);
2511 
2512             // When building against the 10.15 SDK we need to make sure the
2513             // AudioUnit framework is linked before the AudioToolbox framework.
2514             auto audioUnitIndex = s.indexOf ("AudioUnit", false, 1);
2515 
2516             if (audioUnitIndex != -1)
2517             {
2518                 s.remove (audioUnitIndex);
2519                 s.insert (0, "AudioUnit");
2520             }
2521 
2522             for (auto& framework : s)
2523             {
2524                 auto frameworkID = addFramework (framework);
2525 
2526                 // find all the targets that are referring to this object
2527                 for (auto& target : targets)
2528                 {
2529                     if (xcodeFrameworks.contains (framework) || target->xcodeFrameworks.contains (framework))
2530                     {
2531                         target->frameworkIDs.add (frameworkID);
2532                         target->frameworkNames.add (framework);
2533                     }
2534                 }
2535             }
2536         }
2537     }
2538 
addCustomFrameworks()2539     void addCustomFrameworks() const
2540     {
2541         StringArray customFrameworks;
2542         customFrameworks.addTokens (getExtraCustomFrameworksString(), true);
2543         customFrameworks.trim();
2544 
2545         for (auto& framework : customFrameworks)
2546         {
2547             auto frameworkID = addCustomFramework (framework);
2548 
2549             for (auto& target : targets)
2550             {
2551                 target->frameworkIDs.add (frameworkID);
2552                 target->frameworkNames.add (framework);
2553             }
2554         }
2555     }
2556 
addEmbeddedFrameworks()2557     void addEmbeddedFrameworks() const
2558     {
2559         StringArray frameworks;
2560         frameworks.addTokens (getEmbeddedFrameworksString(), true);
2561         frameworks.trim();
2562 
2563         for (auto& framework : frameworks)
2564         {
2565             auto frameworkID = addEmbeddedFramework (framework);
2566             embeddedFrameworkIDs.add (frameworkID);
2567 
2568             for (auto& target : targets)
2569             {
2570                 target->frameworkIDs.add (frameworkID);
2571                 target->frameworkNames.add (framework);
2572             }
2573         }
2574 
2575         if (! embeddedFrameworkIDs.isEmpty())
2576             for (auto& target : targets)
2577                 target->addCopyFilesPhase ("Embed Frameworks", embeddedFrameworkIDs, kFrameworksFolder);
2578     }
2579 
addCustomResourceFolders()2580     void addCustomResourceFolders() const
2581     {
2582         StringArray folders;
2583 
2584         folders.addTokens (getCustomResourceFoldersString(), ":", "");
2585         folders.trim();
2586         folders.removeEmptyStrings();
2587 
2588         for (auto& crf : folders)
2589             addCustomResourceFolder (crf);
2590     }
2591 
addSubprojects()2592     void addSubprojects() const
2593     {
2594         auto subprojectLines = StringArray::fromLines (getSubprojectsString());
2595         subprojectLines.removeEmptyStrings (true);
2596 
2597         struct SubprojectInfo
2598         {
2599             String path;
2600             StringArray buildProducts;
2601         };
2602 
2603         std::vector<SubprojectInfo> subprojects;
2604 
2605         for (auto& line : subprojectLines)
2606         {
2607             String subprojectPath (line.upToFirstOccurrenceOf (":", false, false));
2608 
2609             if (! subprojectPath.endsWith (".xcodeproj"))
2610                 subprojectPath << ".xcodeproj";
2611 
2612             StringArray requestedBuildProducts (StringArray::fromTokens (line.fromFirstOccurrenceOf (":", false, false), ",;|", "\"'"));
2613             requestedBuildProducts.trim();
2614             subprojects.push_back ({ subprojectPath, requestedBuildProducts });
2615         }
2616 
2617         for (const auto& subprojectInfo : subprojects)
2618         {
2619             auto subprojectFile = getTargetFolder().getChildFile (subprojectInfo.path);
2620 
2621             if (! subprojectFile.isDirectory())
2622                 continue;
2623 
2624             auto availableBuildProducts = XcodeProjectParser::parseBuildProducts (subprojectFile);
2625 
2626             if (! subprojectInfo.buildProducts.isEmpty())
2627             {
2628                 auto newEnd = std::remove_if (availableBuildProducts.begin(), availableBuildProducts.end(),
2629                                               [&subprojectInfo] (const XcodeProjectParser::BuildProduct& item)
2630                                               {
2631                                                   return ! subprojectInfo.buildProducts.contains (item.name);
2632                                               });
2633                 availableBuildProducts.erase (newEnd, availableBuildProducts.end());
2634             }
2635 
2636             if (availableBuildProducts.empty())
2637                 continue;
2638 
2639             auto subprojectPath = build_tools::RelativePath (subprojectFile,
2640                                                              getTargetFolder(),
2641                                                              build_tools::RelativePath::buildTargetFolder).toUnixStyle();
2642 
2643             auto subprojectFileType = getFileType (subprojectPath);
2644             auto subprojectFileID = addFileOrFolderReference (subprojectPath, "<group>", subprojectFileType);
2645             subprojectFileIDs.add (subprojectFileID);
2646 
2647             StringArray productIDs;
2648 
2649             for (auto& buildProduct : availableBuildProducts)
2650             {
2651                 auto buildProductFileType = getFileType (buildProduct.path);
2652 
2653                 auto dependencyProxyID = addContainerItemProxy (subprojectFileID, buildProduct.name, "1");
2654                 auto dependencyID = addTargetDependency (dependencyProxyID, buildProduct.name);
2655                 subprojectDependencyIDs.add (dependencyID);
2656 
2657                 auto containerItemProxyReferenceID = addContainerItemProxy (subprojectFileID, buildProduct.name, "2");
2658                 auto proxyID = addReferenceProxy (containerItemProxyReferenceID, buildProduct.path, buildProductFileType);
2659                 productIDs.add (proxyID);
2660 
2661                 if (StringArray { "archive.ar", "compiled.mach-o.dylib", "wrapper.framework" }.contains (buildProductFileType))
2662                 {
2663                     auto buildFileID = addBuildFile (FileOptions().withPath (buildProduct.path)
2664                                                                   .withFileRefID (proxyID)
2665                                                                   .withInhibitWarningsEnabled (true));
2666 
2667                     for (auto& target : targets)
2668                         target->frameworkIDs.add (buildFileID);
2669 
2670                     if (buildProductFileType == "wrapper.framework")
2671                     {
2672                         auto fileID = createID (subprojectPath + "_" + buildProduct.path + "_framework_buildref");
2673 
2674                         ValueTree v (fileID + " /* " + buildProduct.path + " */");
2675                         v.setProperty ("isa", "PBXBuildFile", nullptr);
2676                         v.setProperty ("fileRef", proxyID, nullptr);
2677                         v.setProperty ("settings", "{ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }", nullptr);
2678 
2679                         addObject (v);
2680 
2681                         embeddedFrameworkIDs.add (fileID);
2682                     }
2683                 }
2684             }
2685 
2686             auto productGroupID = createFileRefID (subprojectFile.getFullPathName() + "_products");
2687             addGroup (productGroupID, "Products", productIDs);
2688 
2689             subprojectReferences.add ({ productGroupID, subprojectFileID });
2690         }
2691     }
2692 
addXcassets()2693     void addXcassets() const
2694     {
2695         auto customXcassetsPath = getCustomXcassetsFolderString();
2696 
2697         if (customXcassetsPath.isEmpty())
2698             addDefaultXcassetsFolders();
2699         else
2700             addCustomResourceFolder (customXcassetsPath, "folder.assetcatalog");
2701     }
2702 
2703     void addCustomResourceFolder (String folderPathRelativeToProjectFolder, const String fileType = "folder") const
2704     {
2705         auto folderPath = build_tools::RelativePath (folderPathRelativeToProjectFolder, build_tools::RelativePath::projectFolder)
2706                                        .rebased (projectFolder, getTargetFolder(), build_tools::RelativePath::buildTargetFolder)
2707                                        .toUnixStyle();
2708 
2709         auto fileRefID = createFileRefID (folderPath);
2710 
2711         addFileOrFolderReference (folderPath, "<group>", fileType);
2712 
2713         resourceIDs.add (addBuildFile (FileOptions().withPath (folderPath)
2714                                                     .withFileRefID (fileRefID)));
2715 
2716         resourceFileRefs.add (createFileRefID (folderPath));
2717     }
2718 
2719     //==============================================================================
writeProjectFile(OutputStream & output)2720     void writeProjectFile (OutputStream& output) const
2721     {
2722         output << "// !$*UTF8*$!\n{\n"
2723                   "\tarchiveVersion = 1;\n"
2724                   "\tclasses = {\n\t};\n"
2725                   "\tobjectVersion = 46;\n"
2726                   "\tobjects = {\n";
2727 
2728         StringArray objectTypes;
2729 
2730         for (auto it : objects)
2731             objectTypes.add (it.getType().toString());
2732 
2733         objectTypes.sort (false);
2734 
2735         for (const auto& objectType : objectTypes)
2736         {
2737             auto objectsWithType = objects.getChildWithName (objectType);
2738             auto requiresSingleLine = objectType == "PBXBuildFile" || objectType == "PBXFileReference";
2739 
2740             output << "\n/* Begin " << objectType << " section */\n";
2741 
2742             for (const auto& o : objectsWithType)
2743             {
2744                 auto label = [&o]() -> String
2745                 {
2746                     if (auto* objName = o.getPropertyPointer ("name"))
2747                         return " /* " + objName->toString() + " */";
2748 
2749                     return {};
2750                 }();
2751 
2752                 output << "\t\t" << o.getType().toString() << label << " = {";
2753 
2754                 if (! requiresSingleLine)
2755                     output << "\n";
2756 
2757                 for (int j = 0; j < o.getNumProperties(); ++j)
2758                 {
2759                     auto propertyName = o.getPropertyName (j);
2760                     auto val = o.getProperty (propertyName).toString();
2761 
2762                     if (val.isEmpty() || (val.containsAnyOf (" \t;<>()=,&+-@~\r\n\\#%^`*")
2763                                             && ! (val.trimStart().startsWithChar ('(')
2764                                                     || val.trimStart().startsWithChar ('{'))))
2765                         val = val.quoted();
2766 
2767                     auto content = propertyName.toString() + " = " + val + ";";
2768 
2769                     if (requiresSingleLine)
2770                         content = content + " ";
2771                     else
2772                         content = "\t\t\t" + content + "\n";
2773 
2774                     output << content;
2775                 }
2776 
2777                 if (! requiresSingleLine)
2778                     output << "\t\t";
2779 
2780                 output << "};\n";
2781             }
2782 
2783             output << "/* End " << objectType << " section */\n";
2784         }
2785 
2786         output << "\t};\n\trootObject = " << createID ("__root") << " /* Project object */;\n}\n";
2787     }
2788 
2789     String addFileReference (String pathString, String fileType = {}) const
2790     {
2791         String sourceTree ("SOURCE_ROOT");
2792         build_tools::RelativePath path (pathString, build_tools::RelativePath::unknown);
2793 
2794         if (pathString.startsWith ("${"))
2795         {
2796             sourceTree = pathString.substring (2).upToFirstOccurrenceOf ("}", false, false);
2797             pathString = pathString.fromFirstOccurrenceOf ("}/", false, false);
2798         }
2799         else if (path.isAbsolute())
2800         {
2801             sourceTree = "<absolute>";
2802         }
2803 
2804         return addFileOrFolderReference (pathString, sourceTree, fileType.isEmpty() ? getFileType (pathString) : fileType);
2805     }
2806 
addFileOrFolderReference(const String & pathString,String sourceTree,String fileType)2807     String addFileOrFolderReference (const String& pathString, String sourceTree, String fileType) const
2808     {
2809         auto fileRefID = createFileRefID (pathString);
2810         auto filename = File::createFileWithoutCheckingPath (pathString).getFileName();
2811 
2812         ValueTree v (fileRefID + " /* " + filename + " */");
2813         v.setProperty ("isa", "PBXFileReference", nullptr);
2814         v.setProperty ("lastKnownFileType", fileType, nullptr);
2815         v.setProperty (Ids::name, pathString.fromLastOccurrenceOf ("/", false, false), nullptr);
2816         v.setProperty ("path", pathString, nullptr);
2817         v.setProperty ("sourceTree", sourceTree, nullptr);
2818 
2819         addObject (v);
2820 
2821         return fileRefID;
2822     }
2823 
addContainerItemProxy(const String & subprojectID,const String & itemName,const String & proxyType)2824     String addContainerItemProxy (const String& subprojectID, const String& itemName, const String& proxyType) const
2825     {
2826         auto uniqueString = subprojectID + "_" + itemName + "_" + proxyType;
2827         auto objectID = createFileRefID (uniqueString);
2828 
2829         ValueTree v (objectID + " /* PBXContainerItemProxy */");
2830         v.setProperty ("isa", "PBXContainerItemProxy", nullptr);
2831         v.setProperty ("containerPortal", subprojectID, nullptr);
2832         v.setProperty ("proxyType", proxyType, nullptr);
2833         v.setProperty ("remoteGlobalIDString", createFileRefID (uniqueString + "_global"), nullptr);
2834         v.setProperty ("remoteInfo", itemName, nullptr);
2835 
2836         addObject (v);
2837 
2838         return objectID;
2839     }
2840 
addTargetDependency(const String & proxyID,const String & itemName)2841     String addTargetDependency (const String& proxyID, const String& itemName) const
2842     {
2843         auto objectID = createFileRefID (proxyID + "_" + itemName + "_PBXTargetDependency");
2844 
2845         ValueTree v (objectID);
2846         v.setProperty ("isa", "PBXTargetDependency", nullptr);
2847         v.setProperty ("name", itemName, nullptr);
2848         v.setProperty ("targetProxy", proxyID, nullptr);
2849 
2850         addObject (v);
2851 
2852         return objectID;
2853     }
2854 
addReferenceProxy(const String & remoteRef,const String & path,const String & fileType)2855     String addReferenceProxy (const String& remoteRef, const String& path, const String& fileType) const
2856     {
2857         auto objectID = createFileRefID (remoteRef + "_" + path);
2858 
2859         ValueTree v (objectID + " /* " + path + " */");
2860         v.setProperty ("isa", "PBXReferenceProxy", nullptr);
2861         v.setProperty ("fileType", fileType, nullptr);
2862         v.setProperty ("path", path, nullptr);
2863         v.setProperty ("remoteRef", remoteRef, nullptr);
2864         v.setProperty ("sourceTree", "BUILT_PRODUCTS_DIR", nullptr);
2865 
2866         addObject (v);
2867 
2868         return objectID;
2869     }
2870 
2871 private:
2872     struct FileOptions
2873     {
withPathFileOptions2874         FileOptions& withPath (const String& p)                             { path = p;                  return *this; }
withRelativePathFileOptions2875         FileOptions& withRelativePath (const build_tools::RelativePath& p)  { path = p.toUnixStyle();    return *this; }
withFileRefIDFileOptions2876         FileOptions& withFileRefID (const String& fid)                      { fileRefID = fid;           return *this; }
withCompilerFlagsFileOptions2877         FileOptions& withCompilerFlags (const String& f)                    { compilerFlags = f;         return *this; }
withCompilationEnabledFileOptions2878         FileOptions& withCompilationEnabled (bool e)                        { compile = e;               return *this; }
withAddToBinaryResourcesEnabledFileOptions2879         FileOptions& withAddToBinaryResourcesEnabled (bool e)               { addToBinaryResources = e;  return *this; }
withAddToXcodeResourcesEnabledFileOptions2880         FileOptions& withAddToXcodeResourcesEnabled (bool e)                { addToXcodeResources = e;   return *this; }
withInhibitWarningsEnabledFileOptions2881         FileOptions& withInhibitWarningsEnabled (bool e)                    { inhibitWarnings = e;       return *this; }
withSkipPCHEnabledFileOptions2882         FileOptions& withSkipPCHEnabled (bool e)                            { skipPCH = e;               return *this; }
withXcodeTargetFileOptions2883         FileOptions& withXcodeTarget (XcodeTarget* t)                       { xcodeTarget = t;           return *this; }
2884 
2885         String path;
2886         String fileRefID;
2887         String compilerFlags;
2888         bool compile = false;
2889         bool addToBinaryResources = false;
2890         bool addToXcodeResources = false;
2891         bool inhibitWarnings = false;
2892         bool skipPCH = false;
2893         XcodeTarget* xcodeTarget = nullptr;
2894     };
2895 
getFileType(const String & filePath)2896     static String getFileType (const String& filePath)
2897     {
2898         build_tools::RelativePath file (filePath, build_tools::RelativePath::unknown);
2899 
2900         if (file.hasFileExtension (cppFileExtensions))      return "sourcecode.cpp.cpp";
2901         if (file.hasFileExtension (".mm"))                  return "sourcecode.cpp.objcpp";
2902         if (file.hasFileExtension (".m"))                   return "sourcecode.c.objc";
2903         if (file.hasFileExtension (".c"))                   return "sourcecode.c.c";
2904         if (file.hasFileExtension (headerFileExtensions))   return "sourcecode.c.h";
2905         if (file.hasFileExtension (asmFileExtensions))      return "sourcecode.c.asm";
2906         if (file.hasFileExtension (".framework"))           return "wrapper.framework";
2907         if (file.hasFileExtension (".jpeg;.jpg"))           return "image.jpeg";
2908         if (file.hasFileExtension ("png;gif"))              return "image" + file.getFileExtension();
2909         if (file.hasFileExtension ("html;htm"))             return "text.html";
2910         if (file.hasFileExtension ("xml;zip;wav"))          return "file" + file.getFileExtension();
2911         if (file.hasFileExtension ("txt;rtf"))              return "text" + file.getFileExtension();
2912         if (file.hasFileExtension ("plist"))                return "text.plist.xml";
2913         if (file.hasFileExtension ("entitlements"))         return "text.plist.xml";
2914         if (file.hasFileExtension ("app"))                  return "wrapper.application";
2915         if (file.hasFileExtension ("component;vst;plugin")) return "wrapper.cfbundle";
2916         if (file.hasFileExtension ("xcodeproj"))            return "wrapper.pb-project";
2917         if (file.hasFileExtension ("a"))                    return "archive.ar";
2918         if (file.hasFileExtension ("dylib"))                return "compiled.mach-o.dylib";
2919         if (file.hasFileExtension ("xcassets"))             return "folder.assetcatalog";
2920 
2921         return "file" + file.getFileExtension();
2922     }
2923 
addFile(const FileOptions & opts)2924     String addFile (const FileOptions& opts) const
2925     {
2926         auto refID = addFileReference (opts.path);
2927 
2928         if (opts.compile || opts.addToXcodeResources)
2929         {
2930             auto fileID = addBuildFile (FileOptions (opts).withFileRefID (refID));
2931 
2932             if (opts.addToXcodeResources)
2933             {
2934                 resourceIDs.add (fileID);
2935                 resourceFileRefs.add (refID);
2936             }
2937         }
2938 
2939         return refID;
2940     }
2941 
addBuildFile(const FileOptions & opts)2942     String addBuildFile (const FileOptions& opts) const
2943     {
2944         auto fileID = createID (opts.path + "buildref");
2945         auto filename = File::createFileWithoutCheckingPath (opts.path).getFileName();
2946 
2947         if (opts.compile)
2948         {
2949             if (opts.xcodeTarget != nullptr)
2950                 opts.xcodeTarget->sourceIDs.add (fileID);
2951             else
2952                 sourceIDs.add (fileID);
2953         }
2954 
2955         ValueTree v (fileID + " /* " + filename + " */");
2956         v.setProperty ("isa", "PBXBuildFile", nullptr);
2957         auto fileRefID = opts.fileRefID.isEmpty() ? createFileRefID (opts.path)
2958                                                   : opts.fileRefID;
2959         v.setProperty ("fileRef", fileRefID, nullptr);
2960 
2961         auto compilerFlags = [&opts]
2962         {
2963             return (opts.compilerFlags
2964                     + (opts.inhibitWarnings ? " -w" : String())
2965                     + (opts.skipPCH ? " -D" + BuildConfiguration::getSkipPrecompiledHeaderDefine() : String())).trim();
2966         }();
2967 
2968         if (compilerFlags.isNotEmpty())
2969             v.setProperty ("settings", "{ COMPILER_FLAGS = \"" + compilerFlags + "\"; }", nullptr);
2970 
2971         addObject (v);
2972 
2973         return fileID;
2974     }
2975 
addRezFile(const Project::Item & projectItem,const build_tools::RelativePath & path)2976     String addRezFile (const Project::Item& projectItem, const build_tools::RelativePath& path) const
2977     {
2978         auto refID = addFileReference (path.toUnixStyle());
2979 
2980         if (projectItem.isModuleCode())
2981         {
2982             if (auto* xcodeTarget = getTargetOfType (getProject().getTargetTypeFromFilePath (projectItem.getFile(), false)))
2983             {
2984                 auto rezFileID = addBuildFile (FileOptions().withRelativePath (path)
2985                                                             .withFileRefID (refID)
2986                                                             .withXcodeTarget (xcodeTarget));
2987 
2988                 xcodeTarget->rezFileIDs.add (rezFileID);
2989 
2990                 return refID;
2991             }
2992         }
2993 
2994         return {};
2995     }
2996 
addEntitlementsFile(XcodeTarget & target)2997     void addEntitlementsFile (XcodeTarget& target) const
2998     {
2999         build_tools::EntitlementOptions options;
3000 
3001         options.type                            = target.type;
3002         options.isiOS                           = isiOS();
3003         options.isAudioPluginProject            = project.isAudioPluginProject();
3004         options.shouldEnableIAA                 = project.shouldEnableIAA();
3005         options.isiCloudPermissionsEnabled      = isiCloudPermissionsEnabled();
3006         options.isPushNotificationsEnabled      = isPushNotificationsEnabled();
3007         options.isAppGroupsEnabled              = isAppGroupsEnabled();
3008         options.isHardenedRuntimeEnabled        = isHardenedRuntimeEnabled();
3009         options.isAppSandboxEnabled             = isAppSandboxEnabled();
3010         options.isAppSandboxInhertianceEnabled  = isAppSandboxInhertianceEnabled();
3011         options.appGroupIdString                = getAppGroupIdString();
3012         options.hardenedRuntimeOptions          = getHardenedRuntimeOptions();
3013         options.appSandboxOptions               = getAppSandboxOptions();
3014 
3015         const auto entitlementsFile = getTargetFolder().getChildFile (target.getEntitlementsFilename());
3016         build_tools::overwriteFileIfDifferentOrThrow (entitlementsFile, options.getEntitlementsFileContent());
3017 
3018         build_tools::RelativePath entitlementsPath (entitlementsFile, getTargetFolder(), build_tools::RelativePath::buildTargetFolder);
3019         addFile (FileOptions().withRelativePath (entitlementsPath));
3020     }
3021 
addProjectItem(const Project::Item & projectItem)3022     String addProjectItem (const Project::Item& projectItem) const
3023     {
3024         if (modulesGroup != nullptr && projectItem.getParent() == *modulesGroup)
3025             return addFileReference (rebaseFromProjectFolderToBuildTarget (getModuleFolderRelativeToProject (projectItem.getName())).toUnixStyle(),
3026                                      "folder");
3027 
3028         if (projectItem.isGroup())
3029         {
3030             StringArray childIDs;
3031             for (int i = 0; i < projectItem.getNumChildren(); ++i)
3032             {
3033                 auto child = projectItem.getChild (i);
3034 
3035                 auto childID = addProjectItem (child);
3036 
3037                 if (childID.isNotEmpty() && ! child.shouldBeAddedToXcodeResources())
3038                     childIDs.add (childID);
3039             }
3040 
3041             if (childIDs.isEmpty())
3042                 return {};
3043 
3044             return addGroup (projectItem, childIDs);
3045         }
3046 
3047         if (projectItem.shouldBeAddedToTargetProject() && projectItem.shouldBeAddedToTargetExporter (*this))
3048         {
3049             auto itemPath = projectItem.getFilePath();
3050             build_tools::RelativePath path;
3051 
3052             if (itemPath.startsWith ("${"))
3053                 path = build_tools::RelativePath (itemPath, build_tools::RelativePath::unknown);
3054             else
3055                 path = build_tools::RelativePath (projectItem.getFile(), getTargetFolder(), build_tools::RelativePath::buildTargetFolder);
3056 
3057             if (path.hasFileExtension (".r"))
3058                 return addRezFile (projectItem, path);
3059 
3060             XcodeTarget* xcodeTarget = nullptr;
3061             if (projectItem.isModuleCode() && projectItem.shouldBeCompiled())
3062                 xcodeTarget = getTargetOfType (project.getTargetTypeFromFilePath (projectItem.getFile(), false));
3063 
3064             return addFile (FileOptions().withRelativePath (path)
3065                                          .withCompilerFlags (compilerFlagSchemesMap[projectItem.getCompilerFlagSchemeString()].get())
3066                                          .withCompilationEnabled (projectItem.shouldBeCompiled())
3067                                          .withAddToBinaryResourcesEnabled (projectItem.shouldBeAddedToBinaryResources())
3068                                          .withAddToXcodeResourcesEnabled (projectItem.shouldBeAddedToXcodeResources())
3069                                          .withInhibitWarningsEnabled (projectItem.shouldInhibitWarnings())
3070                                          .withSkipPCHEnabled (isPCHEnabledForAnyConfigurations() && projectItem.shouldSkipPCH())
3071                                          .withXcodeTarget (xcodeTarget));
3072         }
3073 
3074         return {};
3075     }
3076 
addFramework(const String & frameworkName)3077     String addFramework (const String& frameworkName) const
3078     {
3079         auto path = frameworkName;
3080         auto isRelativePath = path.startsWith ("../");
3081 
3082         if (! File::isAbsolutePath (path) && ! isRelativePath)
3083             path = "System/Library/Frameworks/" + path;
3084 
3085         if (! path.endsWithIgnoreCase (".framework"))
3086             path << ".framework";
3087 
3088         auto fileRefID = createFileRefID (path);
3089 
3090         addFileReference (((File::isAbsolutePath (frameworkName) || isRelativePath) ? "" : "${SDKROOT}/") + path);
3091         frameworkFileIDs.add (fileRefID);
3092 
3093         return addBuildFile (FileOptions().withPath (path)
3094                                           .withFileRefID (fileRefID));
3095     }
3096 
addCustomFramework(String frameworkPath)3097     String addCustomFramework (String frameworkPath) const
3098     {
3099         if (! frameworkPath.endsWithIgnoreCase (".framework"))
3100             frameworkPath << ".framework";
3101 
3102         auto fileRefID = createFileRefID (frameworkPath);
3103 
3104         auto fileType = getFileType (frameworkPath);
3105         addFileOrFolderReference (frameworkPath, "<group>", fileType);
3106 
3107         frameworkFileIDs.add (fileRefID);
3108 
3109         return addBuildFile (FileOptions().withPath (frameworkPath)
3110                                           .withFileRefID (fileRefID));
3111     }
3112 
addEmbeddedFramework(const String & path)3113     String addEmbeddedFramework (const String& path) const
3114     {
3115         auto fileRefID = createFileRefID (path);
3116         auto filename = File::createFileWithoutCheckingPath (path).getFileName();
3117 
3118         auto fileType = getFileType (path);
3119         addFileOrFolderReference (path, "<group>", fileType);
3120 
3121         auto fileID = createID (path + "buildref");
3122 
3123         ValueTree v (fileID + " /* " + filename + " */");
3124         v.setProperty ("isa", "PBXBuildFile", nullptr);
3125         v.setProperty ("fileRef", fileRefID, nullptr);
3126         v.setProperty ("settings", "{ ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }", nullptr);
3127 
3128         addObject (v);
3129 
3130         frameworkFileIDs.add (fileRefID);
3131 
3132         return fileID;
3133     }
3134 
addGroup(const String & groupID,const String & groupName,const StringArray & childIDs)3135     void addGroup (const String& groupID, const String& groupName, const StringArray& childIDs) const
3136     {
3137         ValueTree v (groupID);
3138         v.setProperty ("isa", "PBXGroup", nullptr);
3139         v.setProperty ("children", indentParenthesisedList (childIDs), nullptr);
3140         v.setProperty (Ids::name, groupName, nullptr);
3141         v.setProperty ("sourceTree", "<group>", nullptr);
3142 
3143         addObject (v);
3144     }
3145 
addGroup(const Project::Item & item,StringArray & childIDs)3146     String addGroup (const Project::Item& item, StringArray& childIDs) const
3147     {
3148         auto groupName = item.getName();
3149         auto groupID = getIDForGroup (item);
3150         addGroup (groupID, groupName, childIDs);
3151         return groupID;
3152     }
3153 
addProjectConfig(const String & configName,const StringArray & buildSettings)3154     void addProjectConfig (const String& configName, const StringArray& buildSettings) const
3155     {
3156         ValueTree v (createID ("projectconfigid_" + configName));
3157         v.setProperty ("isa", "XCBuildConfiguration", nullptr);
3158         v.setProperty ("buildSettings", indentBracedList (buildSettings), nullptr);
3159         v.setProperty (Ids::name, configName, nullptr);
3160 
3161         addObject (v);
3162     }
3163 
addConfigList(XcodeTarget & target,const String & listID)3164     void addConfigList (XcodeTarget& target, const String& listID) const
3165     {
3166         ValueTree v (listID);
3167         v.setProperty ("isa", "XCConfigurationList", nullptr);
3168         v.setProperty ("buildConfigurations", indentParenthesisedList (target.configIDs), nullptr);
3169         v.setProperty ("defaultConfigurationIsVisible", (int) 0, nullptr);
3170         v.setProperty ("defaultConfigurationName", getConfiguration (0)->getName(), nullptr);
3171 
3172         addObject (v);
3173     }
3174 
addProjectConfigList(const String & listID)3175     void addProjectConfigList (const String& listID) const
3176     {
3177         auto buildConfigs = objects.getChildWithName ("XCBuildConfiguration");
3178         jassert (buildConfigs.isValid());
3179 
3180         StringArray configIDs;
3181 
3182         for (const auto& child : buildConfigs)
3183             configIDs.add (child.getType().toString());
3184 
3185         ValueTree v (listID);
3186         v.setProperty ("isa", "XCConfigurationList", nullptr);
3187         v.setProperty ("buildConfigurations", indentParenthesisedList (configIDs), nullptr);
3188         v.setProperty ("defaultConfigurationIsVisible", (int) 0, nullptr);
3189         v.setProperty ("defaultConfigurationName", getConfiguration (0)->getName(), nullptr);
3190 
3191         addObject (v);
3192     }
3193 
addProjectObject()3194     void addProjectObject() const
3195     {
3196         ValueTree v (createID ("__root"));
3197         v.setProperty ("isa", "PBXProject", nullptr);
3198         v.setProperty ("attributes", indentBracedList (getProjectObjectAttributes()), nullptr);
3199         v.setProperty ("buildConfigurationList", createID ("__projList"), nullptr);
3200         v.setProperty ("compatibilityVersion", "Xcode 3.2", nullptr);
3201         v.setProperty ("hasScannedForEncodings", (int) 0, nullptr);
3202         v.setProperty ("knownRegions", indentParenthesisedList ({ "en", "Base" }), nullptr);
3203         v.setProperty ("mainGroup", createID ("__mainsourcegroup"), nullptr);
3204         v.setProperty ("projectDirPath", "\"\"", nullptr);
3205 
3206         if (! subprojectReferences.isEmpty())
3207         {
3208             StringArray projectReferences;
3209 
3210             for (auto& reference : subprojectReferences)
3211                 projectReferences.add (indentBracedList ({ "ProductGroup = " + reference.productGroup, "ProjectRef = " + reference.projectRef }, 1));
3212 
3213             v.setProperty ("projectReferences", indentParenthesisedList (projectReferences), nullptr);
3214         }
3215 
3216         v.setProperty ("projectRoot", "\"\"", nullptr);
3217 
3218         v.setProperty ("targets", indentParenthesisedList (targetIDs), nullptr);
3219 
3220         addObject (v);
3221     }
3222 
3223     //==============================================================================
removeMismatchedXcuserdata()3224     void removeMismatchedXcuserdata() const
3225     {
3226         if (shouldKeepCustomXcodeSchemes())
3227             return;
3228 
3229         auto xcuserdata = getProjectBundle().getChildFile ("xcuserdata");
3230 
3231         if (! xcuserdata.exists())
3232             return;
3233 
3234         if (! xcuserdataMatchesTargets (xcuserdata))
3235         {
3236             xcuserdata.deleteRecursively();
3237             getProjectBundle().getChildFile ("xcshareddata").getChildFile ("xcschemes").deleteRecursively();
3238             getProjectBundle().getChildFile ("project.xcworkspace").deleteRecursively();
3239         }
3240     }
3241 
xcuserdataMatchesTargets(const File & xcuserdata)3242     bool xcuserdataMatchesTargets (const File& xcuserdata) const
3243     {
3244         for (auto& plist : xcuserdata.findChildFiles (File::findFiles, true, "xcschememanagement.plist"))
3245             if (! xcschemeManagementPlistMatchesTargets (plist))
3246                 return false;
3247 
3248         return true;
3249     }
3250 
parseNamesOfTargetsFromPlist(const XmlElement & dictXML)3251     static StringArray parseNamesOfTargetsFromPlist (const XmlElement& dictXML)
3252     {
3253         for (auto* schemesKey : dictXML.getChildWithTagNameIterator ("key"))
3254         {
3255             if (schemesKey->getAllSubText().trim().equalsIgnoreCase ("SchemeUserState"))
3256             {
3257                 if (auto* dict = schemesKey->getNextElement())
3258                 {
3259                     if (dict->hasTagName ("dict"))
3260                     {
3261                         StringArray names;
3262 
3263                         for (auto* key : dict->getChildWithTagNameIterator ("key"))
3264                             names.add (key->getAllSubText().upToLastOccurrenceOf (".xcscheme", false, false).trim());
3265 
3266                         names.sort (false);
3267                         return names;
3268                     }
3269                 }
3270             }
3271         }
3272 
3273         return {};
3274     }
3275 
getNamesOfTargets()3276     StringArray getNamesOfTargets() const
3277     {
3278         StringArray names;
3279 
3280         for (auto& target : targets)
3281             names.add (target->getXcodeSchemeName());
3282 
3283         names.sort (false);
3284         return names;
3285     }
3286 
xcschemeManagementPlistMatchesTargets(const File & plist)3287     bool xcschemeManagementPlistMatchesTargets (const File& plist) const
3288     {
3289         if (auto xml = parseXML (plist))
3290             if (auto* dict = xml->getChildByName ("dict"))
3291                 return parseNamesOfTargetsFromPlist (*dict) == getNamesOfTargets();
3292 
3293         return false;
3294     }
3295 
getProjectObjectAttributes()3296     StringArray getProjectObjectAttributes() const
3297     {
3298         std::map<String, String> attributes;
3299 
3300         attributes["LastUpgradeCheck"] = "1240";
3301         attributes["ORGANIZATIONNAME"] = getProject().getCompanyNameString().quoted();
3302 
3303         if (projectType.isGUIApplication() || projectType.isAudioPlugin())
3304         {
3305             StringArray targetAttributes;
3306 
3307             for (auto& target : targets)
3308                 targetAttributes.add (target->getTargetAttributes());
3309 
3310             attributes["TargetAttributes"] = indentBracedList (targetAttributes, 1);
3311         }
3312 
3313         StringArray result;
3314 
3315         for (const auto& attrib : attributes)
3316             result.add (attrib.first + " = " + attrib.second);
3317 
3318         return result;
3319     }
3320 
3321     //==============================================================================
writeDefaultLaunchStoryboardFile()3322     void writeDefaultLaunchStoryboardFile() const
3323     {
3324         const auto storyboardFile = getTargetFolder().getChildFile (getDefaultLaunchStoryboardName() + ".storyboard");
3325 
3326         build_tools::writeStreamToFile (storyboardFile, [&] (MemoryOutputStream& mo)
3327         {
3328             mo << String (BinaryData::LaunchScreen_storyboard);
3329         });
3330 
3331         addLaunchStoryboardFileReference (build_tools::RelativePath (storyboardFile,
3332                                                                      getTargetFolder(),
3333                                                                      build_tools::RelativePath::buildTargetFolder));
3334     }
3335 
addLaunchStoryboardFileReference(const build_tools::RelativePath & relativePath)3336     void addLaunchStoryboardFileReference (const build_tools::RelativePath& relativePath) const
3337     {
3338         auto path = relativePath.toUnixStyle();
3339 
3340         auto refID  = addFileReference (path);
3341         auto fileID = addBuildFile (FileOptions().withPath (path)
3342                                                  .withFileRefID (refID));
3343 
3344         resourceIDs.add (fileID);
3345         resourceFileRefs.add (refID);
3346     }
3347 
addDefaultXcassetsFolders()3348     void addDefaultXcassetsFolders() const
3349     {
3350         const auto assetsPath = build_tools::createXcassetsFolderFromIcons (getIcons(),
3351                                                                             getTargetFolder(),
3352                                                                             project.getProjectFilenameRootString());
3353         addFileReference (assetsPath.toUnixStyle());
3354         resourceIDs.add (addBuildFile (FileOptions().withRelativePath (assetsPath)));
3355         resourceFileRefs.add (createFileRefID (assetsPath));
3356     }
3357 
3358     //==============================================================================
3359     static String indentBracedList        (const StringArray& list, int depth = 0) { return indentList (list, '{', '}', ";", depth, true); }
3360     static String indentParenthesisedList (const StringArray& list, int depth = 0) { return indentList (list, '(', ')', ",", depth, false); }
3361 
indentList(StringArray list,char openBracket,char closeBracket,const String & separator,int extraTabs,bool shouldSort)3362     static String indentList (StringArray list, char openBracket, char closeBracket, const String& separator, int extraTabs, bool shouldSort)
3363     {
3364         auto content = [extraTabs, shouldSort, &list, &separator] () -> String
3365         {
3366             if (list.isEmpty())
3367                 return "";
3368 
3369             if (shouldSort)
3370                 list.sort (true);
3371 
3372             auto tabs = String::repeatedString ("\t", extraTabs + 4);
3373             return tabs + list.joinIntoString (separator + "\n" + tabs) + separator + "\n";
3374         }();
3375 
3376         return openBracket + String ("\n")
3377             + content
3378             + String::repeatedString ("\t", extraTabs + 3) + closeBracket;
3379     }
3380 
createID(String rootString)3381     String createID (String rootString) const
3382     {
3383         if (rootString.startsWith ("${"))
3384             rootString = rootString.fromFirstOccurrenceOf ("}/", false, false);
3385 
3386         rootString += project.getProjectUIDString();
3387 
3388         return MD5 (rootString.toUTF8()).toHexString().substring (0, 24).toUpperCase();
3389     }
3390 
createFileRefID(const build_tools::RelativePath & path)3391     String createFileRefID (const build_tools::RelativePath& path) const { return createFileRefID (path.toUnixStyle()); }
createFileRefID(const String & path)3392     String createFileRefID (const String& path) const                    { return createID ("__fileref_" + path); }
getIDForGroup(const Project::Item & item)3393     String getIDForGroup (const Project::Item& item) const               { return createID (item.getID()); }
3394 
shouldFileBeCompiledByDefault(const File & file)3395     bool shouldFileBeCompiledByDefault (const File& file) const override
3396     {
3397         return file.hasFileExtension (sourceFileExtensions);
3398     }
3399 
3400     //==============================================================================
updateOldOrientationSettings()3401     void updateOldOrientationSettings()
3402     {
3403         jassert (iOS);
3404 
3405         StringArray orientationSettingStrings { getSetting (Ids::iPhoneScreenOrientation).getValue().toString(),
3406                                                 getSetting (Ids::iPadScreenOrientation).getValue().toString() };
3407 
3408         for (int i = 0; i < 2; ++i)
3409         {
3410             auto& settingsString = orientationSettingStrings[i];
3411 
3412             if (settingsString.isNotEmpty())
3413             {
3414                 Array<var> orientations;
3415 
3416                 if (settingsString.contains ("portrait"))   orientations.add ("UIInterfaceOrientationPortrait");
3417                 if (settingsString.contains ("landscape"))  orientations.addArray ({ "UIInterfaceOrientationLandscapeLeft",
3418                                                                                      "UIInterfaceOrientationLandscapeRight" });
3419 
3420                 if (! orientations.isEmpty())
3421                 {
3422                     if (i == 0)
3423                         iPhoneScreenOrientationValue = orientations;
3424                     else
3425                         iPadScreenOrientationValue = orientations;
3426                 }
3427             }
3428         }
3429     }
3430 
addObject(ValueTree data)3431     void addObject (ValueTree data) const
3432     {
3433         if (auto* type = data.getPropertyPointer ("isa"))
3434         {
3435             auto objs = objects.getOrCreateChildWithName (type->toString(), nullptr);
3436             auto objectID = data.getType();
3437             auto numChildren = objs.getNumChildren();
3438 
3439             for (int i = 0; i < numChildren; ++i)
3440             {
3441                 auto obj = objs.getChild (i);
3442                 auto childID = obj.getType();
3443 
3444                 if (objectID < childID)
3445                 {
3446                     objs.addChild (data, i, nullptr);
3447                     return;
3448                 }
3449 
3450                 if (objectID == childID)
3451                 {
3452                     jassert (obj.isEquivalentTo (data));
3453                     return;
3454                 }
3455             }
3456 
3457             objs.appendChild (data, nullptr);
3458             return;
3459         }
3460 
3461         jassertfalse;
3462     }
3463 
3464     //==============================================================================
3465     friend class CLionProjectExporter;
3466 
3467     bool xcodeCanUseDwarf;
3468     OwnedArray<XcodeTarget> targets;
3469 
3470     mutable ValueTree objects { "objects" };
3471 
3472     mutable StringArray resourceIDs, sourceIDs, targetIDs, frameworkFileIDs, embeddedFrameworkIDs,
3473                         rezFileIDs, resourceFileRefs, subprojectFileIDs, subprojectDependencyIDs;
3474 
3475     struct SubprojectReferenceInfo
3476     {
3477         String productGroup, projectRef;
3478     };
3479 
3480     mutable Array<SubprojectReferenceInfo> subprojectReferences;
3481     mutable File menuNibFile, iconFile;
3482     mutable StringArray buildProducts;
3483 
3484     const bool iOS;
3485 
3486     ValueWithDefault customPListValue, pListPrefixHeaderValue, pListPreprocessValue,
3487                      subprojectsValue,
3488                      validArchsValue,
3489                      extraFrameworksValue, frameworkSearchPathsValue, extraCustomFrameworksValue, embeddedFrameworksValue,
3490                      postbuildCommandValue, prebuildCommandValue,
3491                      duplicateAppExResourcesFolderValue, iosDeviceFamilyValue, iPhoneScreenOrientationValue,
3492                      iPadScreenOrientationValue, customXcodeResourceFoldersValue, customXcassetsFolderValue,
3493                      appSandboxValue, appSandboxInheritanceValue, appSandboxOptionsValue,
3494                      hardenedRuntimeValue, hardenedRuntimeOptionsValue,
3495                      microphonePermissionNeededValue, microphonePermissionsTextValue,
3496                      cameraPermissionNeededValue, cameraPermissionTextValue,
3497                      bluetoothPermissionNeededValue, bluetoothPermissionTextValue,
3498                      sendAppleEventsPermissionNeededValue, sendAppleEventsPermissionTextValue,
3499                      uiFileSharingEnabledValue, uiSupportsDocumentBrowserValue, uiStatusBarHiddenValue, uiRequiresFullScreenValue, documentExtensionsValue, iosInAppPurchasesValue,
3500                      iosContentSharingValue, iosBackgroundAudioValue, iosBackgroundBleValue, iosPushNotificationsValue, iosAppGroupsValue, iCloudPermissionsValue,
3501                      iosDevelopmentTeamIDValue, iosAppGroupsIDValue, keepCustomXcodeSchemesValue, useHeaderMapValue, customLaunchStoryboardValue,
3502                      exporterBundleIdentifierValue, suppressPlistResourceUsageValue, useLegacyBuildSystemValue;
3503 
3504     JUCE_DECLARE_NON_COPYABLE (XcodeProjectExporter)
3505 };
3506