The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

3091 lines
138KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #pragma once
  20. //==============================================================================
  21. namespace
  22. {
  23. const char* const osxVersionDefault = "default";
  24. const int oldestSDKVersion = 5;
  25. const int currentSDKVersion = 13;
  26. const int minimumAUv3SDKVersion = 11;
  27. const char* const osxArch_Default = "default";
  28. const char* const osxArch_Native = "Native";
  29. const char* const osxArch_32BitUniversal = "32BitUniversal";
  30. const char* const osxArch_64BitUniversal = "64BitUniversal";
  31. const char* const osxArch_64Bit = "64BitIntel";
  32. }
  33. //==============================================================================
  34. class XcodeProjectExporter : public ProjectExporter
  35. {
  36. public:
  37. //==============================================================================
  38. static const char* getNameMac() { return "Xcode (MacOSX)"; }
  39. static const char* getNameiOS() { return "Xcode (iOS)"; }
  40. static const char* getValueTreeTypeName (bool iOS) { return iOS ? "XCODE_IPHONE" : "XCODE_MAC"; }
  41. //==============================================================================
  42. XcodeProjectExporter (Project& p, const ValueTree& t, const bool isIOS)
  43. : ProjectExporter (p, t),
  44. xcodeCanUseDwarf (true),
  45. iOS (isIOS)
  46. {
  47. name = iOS ? getNameiOS() : getNameMac();
  48. if (getTargetLocationString().isEmpty())
  49. getTargetLocationValue() = getDefaultBuildsRootFolder() + (iOS ? "iOS" : "MacOSX");
  50. if (iOS)
  51. {
  52. if (getiPhoneScreenOrientationValue().toString().isEmpty())
  53. getiPhoneScreenOrientationValue() = "portraitlandscape";
  54. if (getiPadScreenOrientationValue().toString().isEmpty())
  55. getiPadScreenOrientationValue() = getiPhoneScreenOrientationString();
  56. if (getDeviceFamilyValue().toString().isEmpty())
  57. getDeviceFamilyValue() = "1,2";
  58. }
  59. }
  60. static XcodeProjectExporter* createForSettings (Project& project, const ValueTree& settings)
  61. {
  62. if (settings.hasType (getValueTreeTypeName (false))) return new XcodeProjectExporter (project, settings, false);
  63. if (settings.hasType (getValueTreeTypeName (true))) return new XcodeProjectExporter (project, settings, true);
  64. return nullptr;
  65. }
  66. //==============================================================================
  67. Value getPListToMergeValue() { return getSetting ("customPList"); }
  68. String getPListToMergeString() const { return settings ["customPList"]; }
  69. Value getPListPrefixHeaderValue() { return getSetting ("PListPrefixHeader"); }
  70. String getPListPrefixHeaderString() const { return settings ["PListPrefixHeader"]; }
  71. Value getPListPreprocessValue() { return getSetting ("PListPreprocess"); }
  72. bool isPListPreprocessEnabled() const { return settings ["PListPreprocess"]; }
  73. Value getExtraFrameworksValue() { return getSetting (Ids::extraFrameworks); }
  74. String getExtraFrameworksString() const { return settings [Ids::extraFrameworks]; }
  75. Value getPostBuildScriptValue() { return getSetting (Ids::postbuildCommand); }
  76. String getPostBuildScript() const { return settings [Ids::postbuildCommand]; }
  77. Value getPreBuildScriptValue() { return getSetting (Ids::prebuildCommand); }
  78. String getPreBuildScript() const { return settings [Ids::prebuildCommand]; }
  79. Value getDuplicateResourcesFolderForAppExtensionValue() { return getSetting (Ids::iosAppExtensionDuplicateResourcesFolder); }
  80. bool shouldDuplicateResourcesFolderForAppExtension() const { return settings [Ids::iosAppExtensionDuplicateResourcesFolder]; }
  81. Value getDeviceFamilyValue() { return getSetting (Ids::iosDeviceFamily); }
  82. String getDeviceFamilyString() const { return settings [Ids::iosDeviceFamily]; }
  83. Value getiPhoneScreenOrientationValue() { return getSetting (Ids::iPhoneScreenOrientation); }
  84. String getiPhoneScreenOrientationString() const { return settings [Ids::iPhoneScreenOrientation]; }
  85. Value getiPadScreenOrientationValue() { return getSetting (Ids::iPadScreenOrientation); }
  86. String getiPadScreenOrientationString() const { return settings [Ids::iPadScreenOrientation]; }
  87. Value getCustomResourceFoldersValue() { return getSetting (Ids::customXcodeResourceFolders); }
  88. String getCustomResourceFoldersString() const { return getSettingString (Ids::customXcodeResourceFolders).replaceCharacters ("\r\n", "::"); }
  89. Value getCustomXcassetsFolderValue() { return getSetting (Ids::customXcassetsFolder); }
  90. String getCustomXcassetsFolderString() const { return settings [Ids::customXcassetsFolder]; }
  91. Value getMicrophonePermissionValue() { return getSetting (Ids::microphonePermissionNeeded); }
  92. bool isMicrophonePermissionEnabled() const { return settings [Ids::microphonePermissionNeeded]; }
  93. Value getInAppPurchasesValue() { return getSetting (Ids::iosInAppPurchases); }
  94. bool isInAppPurchasesEnabled() const { return settings [Ids::iosInAppPurchases]; }
  95. Value getBackgroundAudioValue() { return getSetting (Ids::iosBackgroundAudio); }
  96. bool isBackgroundAudioEnabled() const { return settings [Ids::iosBackgroundAudio]; }
  97. Value getBackgroundBleValue() { return getSetting (Ids::iosBackgroundBle); }
  98. bool isBackgroundBleEnabled() const { return settings [Ids::iosBackgroundBle]; }
  99. Value getPushNotificationsValue() { return getSetting (Ids::iosPushNotifications); }
  100. bool isPushNotificationsEnabled() const { return settings [Ids::iosPushNotifications]; }
  101. Value getAppGroupsEnabledValue() { return getSetting (Ids::iosAppGroups); }
  102. bool isAppGroupsEnabled() const { return settings [Ids::iosAppGroups]; }
  103. Value getiCloudPermissionsEnabled() { return getSetting (Ids::iCloudPermissions); }
  104. bool isiCloudPermissionsEnabled() const { return settings [Ids::iCloudPermissions]; }
  105. Value getIosDevelopmentTeamIDValue() { return getSetting (Ids::iosDevelopmentTeamID); }
  106. String getIosDevelopmentTeamIDString() const { return settings [Ids::iosDevelopmentTeamID]; }
  107. Value getAppGroupIdValue() { return getSetting (Ids::iosAppGroupsId); }
  108. String getAppGroupIdString() const { return settings [Ids::iosAppGroupsId]; }
  109. bool usesMMFiles() const override { return true; }
  110. bool canCopeWithDuplicateFiles() override { return true; }
  111. bool supportsUserDefinedConfigurations() const override { return true; }
  112. bool isXcode() const override { return true; }
  113. bool isVisualStudio() const override { return false; }
  114. bool isCodeBlocks() const override { return false; }
  115. bool isMakefile() const override { return false; }
  116. bool isAndroidStudio() const override { return false; }
  117. bool isCLion() const override { return false; }
  118. bool isAndroid() const override { return false; }
  119. bool isWindows() const override { return false; }
  120. bool isLinux() const override { return false; }
  121. bool isOSX() const override { return ! iOS; }
  122. bool isiOS() const override { return iOS; }
  123. bool supportsTargetType (ProjectType::Target::Type type) const override
  124. {
  125. switch (type)
  126. {
  127. case ProjectType::Target::AudioUnitv3PlugIn:
  128. case ProjectType::Target::StandalonePlugIn:
  129. case ProjectType::Target::GUIApp:
  130. case ProjectType::Target::StaticLibrary:
  131. case ProjectType::Target::SharedCodeTarget:
  132. case ProjectType::Target::AggregateTarget:
  133. return true;
  134. case ProjectType::Target::ConsoleApp:
  135. case ProjectType::Target::VSTPlugIn:
  136. case ProjectType::Target::VST3PlugIn:
  137. case ProjectType::Target::AAXPlugIn:
  138. case ProjectType::Target::RTASPlugIn:
  139. case ProjectType::Target::AudioUnitPlugIn:
  140. case ProjectType::Target::DynamicLibrary:
  141. return ! iOS;
  142. default:
  143. break;
  144. }
  145. return false;
  146. }
  147. void createExporterProperties (PropertyListBuilder& props) override
  148. {
  149. if (iOS)
  150. {
  151. props.add (new TextPropertyComponent (getCustomXcassetsFolderValue(), "Custom Xcassets folder", 128, false),
  152. "If this field is not empty, your Xcode project will use the custom xcassets folder specified here "
  153. "for the app icons and launchimages, and will ignore the Icon files specified above.");
  154. }
  155. props.add (new TextPropertyComponent (getCustomResourceFoldersValue(), "Custom Xcode Resource folders", 8192, true),
  156. "You can specify a list of custom resource folders here (separated by newlines or whitespace). "
  157. "References to these folders will then be added to the Xcode resources. "
  158. "This way you can specify them for OS X and iOS separately, and modify the content of the resource folders "
  159. "without re-saving the Projucer project.");
  160. if (iOS)
  161. {
  162. if (getProject().getProjectType().isAudioPlugin())
  163. props.add (new BooleanPropertyComponent (getDuplicateResourcesFolderForAppExtensionValue(),
  164. "Don't add resources folder to app extension", "Enabled"),
  165. "Enable this to prevent the Projucer from creating a resources folder for AUv3 app extensions.");
  166. static const char* deviceFamilies[] = { "iPhone", "iPad", "Universal", nullptr};
  167. static const char* deviceFamilyValues[] = { "1", "2", "1,2" };
  168. props.add (new ChoicePropertyComponent (getDeviceFamilyValue(), "Device Family", StringArray (deviceFamilies), Array<var> (deviceFamilyValues)),
  169. "The device family to target.");
  170. static const char* iOSOrientations[] = { "Portrait and Landscape", "Portrait", "Landscape", nullptr };
  171. static const char* orientationValues[] = { "portraitlandscape", "portrait", "landscape", nullptr };
  172. props.add (new ChoicePropertyComponent (getiPhoneScreenOrientationValue(), "iPhone Screen orientation", StringArray (iOSOrientations), Array<var> (orientationValues)),
  173. "The screen orientations that this app should support on iPhones.");
  174. props.add (new ChoicePropertyComponent (getiPadScreenOrientationValue(), "iPad Screen orientation", StringArray (iOSOrientations), Array<var> (orientationValues)),
  175. "The screen orientations that this app should support on iPad.");
  176. props.add (new BooleanPropertyComponent (getSetting ("UIFileSharingEnabled"), "File Sharing Enabled", "Enabled"),
  177. "Enable this to expose your app's files to iTunes.");
  178. props.add (new BooleanPropertyComponent (getSetting ("UISupportsDocumentBrowser"), "Support Document Browser", "Enabled"),
  179. "Enable this to allow the user to access your app documents from a native file chooser.");
  180. props.add (new BooleanPropertyComponent (getSetting ("UIStatusBarHidden"), "Status Bar Hidden", "Enabled"),
  181. "Enable this to disable the status bar in your app.");
  182. props.add (new BooleanPropertyComponent (getMicrophonePermissionValue(), "Microphone access", "Enabled"),
  183. "Enable this to allow your app to use the microphone. "
  184. "The user of your app will be prompted to grant microphone access permissions.");
  185. }
  186. else if (projectType.isGUIApplication())
  187. {
  188. props.add (new TextPropertyComponent (getSetting ("documentExtensions"), "Document file extensions", 128, false),
  189. "A comma-separated list of file extensions for documents that your app can open. "
  190. "Using a leading '.' is optional, and the extensions are not case-sensitive.");
  191. }
  192. props.add (new BooleanPropertyComponent (getInAppPurchasesValue(), "In-App purchases capability", "Enabled"),
  193. "Enable this to grant your app the capability for in-app purchases. "
  194. "This option requires that you specify a valid Development Team ID.");
  195. if (iOS)
  196. {
  197. props.add (new BooleanPropertyComponent (getBackgroundAudioValue(), "Audio background capability", "Enabled"),
  198. "Enable this to grant your app the capability to access audio when in background mode.");
  199. props.add (new BooleanPropertyComponent (getBackgroundBleValue(), "Bluetooth MIDI background capability", "Enabled"),
  200. "Enable this to grant your app the capability to connect to Bluetooth LE devices when in background mode.");
  201. props.add (new BooleanPropertyComponent (getAppGroupsEnabledValue(), "App groups capability", "Enabled"),
  202. "Enable this to grant your app the capability to share resources between apps using the same app group ID.");
  203. props.add (new BooleanPropertyComponent (getiCloudPermissionsEnabled(), "iCloud Permissions", "Enabled"),
  204. "Enable this to grant your app the capability to use native file load/save browser windows on iOS.");
  205. }
  206. props.add (new BooleanPropertyComponent (getPushNotificationsValue(), "Push Notifications capability", "Enabled"),
  207. "Enable this to grant your app the capability to receive push notifications.");
  208. props.add (new TextPropertyComponent (getPListToMergeValue(), "Custom PList", 8192, true),
  209. "You can paste the contents of an XML PList file in here, and the settings that it contains will override any "
  210. "settings that the Projucer creates. BEWARE! When doing this, be careful to remove from the XML any "
  211. "values that you DO want the Projucer to change!");
  212. props.add (new BooleanPropertyComponent (getPListPreprocessValue(), "PList Preprocess", "Enabled"),
  213. "Enable this to preprocess PList file. This will allow you to set values to preprocessor defines,"
  214. " for instance if you define: #define MY_FLAG 1 in a prefix header file (see PList prefix header), you can have"
  215. " a key with MY_FLAG value and it will be replaced with 1.");
  216. props.add (new TextPropertyComponent (getPListPrefixHeaderValue(), "PList Prefix Header", 512, false),
  217. "Header file containing definitions used in plist file (see PList Preprocess).");
  218. props.add (new TextPropertyComponent (getExtraFrameworksValue(), "Extra Frameworks", 2048, false),
  219. "A comma-separated list of extra frameworks that should be added to the build. "
  220. "(Don't include the .framework extension in the name)");
  221. props.add (new TextPropertyComponent (getPreBuildScriptValue(), "Pre-build shell script", 32768, true),
  222. "Some shell-script that will be run before a build starts.");
  223. props.add (new TextPropertyComponent (getPostBuildScriptValue(), "Post-build shell script", 32768, true),
  224. "Some shell-script that will be run after a build completes.");
  225. props.add (new TextPropertyComponent (getIosDevelopmentTeamIDValue(), "Development Team ID", 10, false),
  226. "The Development Team ID to be used for setting up code-signing your iOS app. This is a ten-character "
  227. "string (for example, \"S7B6T5XJ2Q\") that describes the distribution certificate Apple issued to you. "
  228. "You can find this string in the OS X app Keychain Access under \"Certificates\".");
  229. if (iOS)
  230. props.add (new TextPropertyComponentWithEnablement (getAppGroupIdValue(), getAppGroupsEnabledValue(), "App Group ID", 256, false),
  231. "The App Group ID to be used for allowing multiple apps to access a shared resource folder. Multiple IDs can be "
  232. "added separated by a semicolon.");
  233. props.add (new BooleanPropertyComponent (getSetting ("keepCustomXcodeSchemes"), "Keep custom Xcode schemes", "Enabled"),
  234. "Enable this to keep any Xcode schemes you have created for debugging or running, e.g. to launch a plug-in in"
  235. "various hosts. If disabled, all schemes are replaced by a default set.");
  236. props.add (new BooleanPropertyComponent (getSetting ("useHeaderMap"), "USE_HEADERMAP", "Enabled"),
  237. "Enable this to make Xcode search all the projects folders for include files. This means you can be lazy "
  238. "and not bother using relative paths to include your headers, but it means your code won't be "
  239. "compatible with other build systems");
  240. }
  241. bool launchProject() override
  242. {
  243. #if JUCE_MAC
  244. return getProjectBundle().startAsProcess();
  245. #else
  246. return false;
  247. #endif
  248. }
  249. bool canLaunchProject() override
  250. {
  251. #if JUCE_MAC
  252. return true;
  253. #else
  254. return false;
  255. #endif
  256. }
  257. //==============================================================================
  258. void create (const OwnedArray<LibraryModule>&) const override
  259. {
  260. for (auto& target : targets)
  261. if (target->shouldCreatePList())
  262. target->infoPlistFile = getTargetFolder().getChildFile (target->getInfoPlistName());
  263. menuNibFile = getTargetFolder().getChildFile ("RecentFilesMenuTemplate.nib");
  264. createIconFile();
  265. File projectBundle (getProjectBundle());
  266. createDirectoryOrThrow (projectBundle);
  267. createObjects();
  268. File projectFile (projectBundle.getChildFile ("project.pbxproj"));
  269. {
  270. MemoryOutputStream mo;
  271. writeProjectFile (mo);
  272. overwriteFileIfDifferentOrThrow (projectFile, mo);
  273. }
  274. writeInfoPlistFiles();
  275. // Deleting the .rsrc files can be needed to force Xcode to update the version number.
  276. deleteRsrcFiles (getTargetFolder().getChildFile ("build"));
  277. }
  278. //==============================================================================
  279. void addPlatformSpecificSettingsForProjectType (const ProjectType&) override
  280. {
  281. callForAllSupportedTargets ([this] (ProjectType::Target::Type targetType)
  282. {
  283. if (auto* target = new XcodeTarget (targetType, *this))
  284. {
  285. if (targetType == ProjectType::Target::AggregateTarget)
  286. targets.insert (0, target);
  287. else
  288. targets.add (target);
  289. }
  290. });
  291. // If you hit this assert, you tried to generate a project for an exporter
  292. // that does not support any of your targets!
  293. jassert (targets.size() > 0);
  294. }
  295. void updateDeprecatedProjectSettingsInteractively() override
  296. {
  297. if (hasInvalidPostBuildScript())
  298. {
  299. String alertWindowText = iOS ? "Your Xcode (iOS) Exporter settings use an invalid post-build script. Click 'Update' to remove it."
  300. : "Your Xcode (OSX) 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"
  301. "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"
  302. "Click 'Update' to remove the script (otherwise your plug-in may not compile correctly).";
  303. if (AlertWindow::showOkCancelBox (AlertWindow::WarningIcon,
  304. "Project settings: " + project.getDocumentTitle(),
  305. alertWindowText, "Update", "Cancel", nullptr, nullptr))
  306. getPostBuildScriptValue() = var();
  307. }
  308. }
  309. bool hasInvalidPostBuildScript() const
  310. {
  311. // check whether the script is identical to the old one that the Introjucer used to auto-generate
  312. return (MD5 (getPostBuildScript().toUTF8()).toHexString() == "265ac212a7e734c5bbd6150e1eae18a1");
  313. }
  314. //==============================================================================
  315. void initialiseDependencyPathValues() override
  316. {
  317. vst3Path.referTo (Value (new DependencyPathValueSource (getSetting (Ids::vst3Folder), Ids::vst3Path, TargetOS::osx)));
  318. aaxPath. referTo (Value (new DependencyPathValueSource (getSetting (Ids::aaxFolder), Ids::aaxPath, TargetOS::osx)));
  319. rtasPath.referTo (Value (new DependencyPathValueSource (getSetting (Ids::rtasFolder), Ids::rtasPath, TargetOS::osx)));
  320. }
  321. protected:
  322. //==============================================================================
  323. class XcodeBuildConfiguration : public BuildConfiguration
  324. {
  325. public:
  326. XcodeBuildConfiguration (Project& p, const ValueTree& t, const bool isIOS, const ProjectExporter& e)
  327. : BuildConfiguration (p, t, e),
  328. iOS (isIOS),
  329. osxSDKVersion (config, Ids::osxSDK, nullptr, "default"),
  330. osxDeploymentTarget (config, Ids::osxCompatibility, nullptr, "default"),
  331. iosDeploymentTarget (config, Ids::iosCompatibility, nullptr, "default"),
  332. osxArchitecture (config, Ids::osxArchitecture, nullptr, "default"),
  333. customXcodeFlags (config, Ids::customXcodeFlags, nullptr),
  334. plistPreprocessorDefinitions (config, Ids::plistPreprocessorDefinitions, nullptr),
  335. cppStandardLibrary (config, Ids::cppLibType, nullptr),
  336. codeSignIdentity (config, Ids::codeSigningIdentity, nullptr, iOS ? "iPhone Developer" : "Mac Developer"),
  337. fastMathEnabled (config, Ids::fastMath, nullptr),
  338. stripLocalSymbolsEnabled (config, Ids::stripLocalSymbols, nullptr),
  339. pluginBinaryCopyStepEnabled (config, Ids::enablePluginBinaryCopyStep, nullptr),
  340. vstBinaryLocation (config, Ids::vstBinaryLocation, nullptr, "$(HOME)/Library/Audio/Plug-Ins/VST/"),
  341. vst3BinaryLocation (config, Ids::vst3BinaryLocation, nullptr, "$(HOME)/Library/Audio/Plug-Ins/VST3/"),
  342. auBinaryLocation (config, Ids::auBinaryLocation, nullptr, "$(HOME)/Library/Audio/Plug-Ins/Components/"),
  343. rtasBinaryLocation (config, Ids::rtasBinaryLocation, nullptr, "/Library/Application Support/Digidesign/Plug-Ins/"),
  344. aaxBinaryLocation (config, Ids::aaxBinaryLocation, nullptr, "/Library/Application Support/Avid/Audio/Plug-Ins/")
  345. {
  346. setValueIfVoid (pluginBinaryCopyStepEnabled.getPropertyAsValue(), true);
  347. updateOldPluginBinaryLocations();
  348. }
  349. //==========================================================================
  350. bool iOS;
  351. CachedValue<String> osxSDKVersion, osxDeploymentTarget, iosDeploymentTarget, osxArchitecture,
  352. customXcodeFlags, plistPreprocessorDefinitions, cppStandardLibrary, codeSignIdentity;
  353. CachedValue<bool> fastMathEnabled, stripLocalSymbolsEnabled, pluginBinaryCopyStepEnabled;
  354. CachedValue<String> vstBinaryLocation, vst3BinaryLocation, auBinaryLocation, rtasBinaryLocation, aaxBinaryLocation;
  355. //==========================================================================
  356. var getDefaultOptimisationLevel() const override { return var ((int) (isDebug() ? gccO0 : gccO3)); }
  357. void createConfigProperties (PropertyListBuilder& props) override
  358. {
  359. addXcodePluginInstallPathProperties (props);
  360. addGCCOptimisationProperty (props);
  361. if (iOS)
  362. {
  363. const char* iosVersions[] = { "Use Default", "7.0", "7.1", "8.0", "8.1", "8.2", "8.3", "8.4", "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3", "11.0", nullptr };
  364. const char* iosVersionValues[] = { osxVersionDefault, "7.0", "7.1", "8.0", "8.1", "8.2", "8.3", "8.4", "9.0", "9.1", "9.2", "9.3", "10.0", "10.1", "10.2", "10.3", "11.0", nullptr };
  365. props.add (new ChoicePropertyComponent (iosDeploymentTarget.getPropertyAsValue(), "iOS Deployment Target",
  366. StringArray (iosVersions), Array<var> (iosVersionValues)),
  367. "The minimum version of iOS that the target binary will run on.");
  368. }
  369. else
  370. {
  371. StringArray sdkVersionNames, osxVersionNames;
  372. Array<var> versionValues;
  373. sdkVersionNames.add ("Use Default");
  374. osxVersionNames.add ("Use Default");
  375. versionValues.add (osxVersionDefault);
  376. for (int ver = oldestSDKVersion; ver <= currentSDKVersion; ++ver)
  377. {
  378. sdkVersionNames.add (getSDKName (ver));
  379. osxVersionNames.add (getOSXVersionName (ver));
  380. versionValues.add (getSDKName (ver));
  381. }
  382. props.add (new ChoicePropertyComponent (osxSDKVersion.getPropertyAsValue(), "OSX Base SDK Version", sdkVersionNames, versionValues),
  383. "The version of OSX to link against in the Xcode build.");
  384. props.add (new ChoicePropertyComponent (osxDeploymentTarget.getPropertyAsValue(), "OSX Deployment Target", osxVersionNames, versionValues),
  385. "The minimum version of OSX that the target binary will be compatible with.");
  386. const char* osxArch[] = { "Use Default", "Native architecture of build machine",
  387. "Universal Binary (32-bit)", "Universal Binary (32/64-bit)", "64-bit Intel", 0 };
  388. const char* osxArchValues[] = { osxArch_Default, osxArch_Native, osxArch_32BitUniversal,
  389. osxArch_64BitUniversal, osxArch_64Bit, 0 };
  390. props.add (new ChoicePropertyComponent (osxArchitecture.getPropertyAsValue(), "OSX Architecture",
  391. StringArray (osxArch), Array<var> (osxArchValues)),
  392. "The type of OSX binary that will be produced.");
  393. }
  394. props.add (new TextPropertyComponent (customXcodeFlags.getPropertyAsValue(), "Custom Xcode flags", 8192, false),
  395. "A comma-separated list of custom Xcode setting flags which will be appended to the list of generated flags, "
  396. "e.g. MACOSX_DEPLOYMENT_TARGET_i386 = 10.5, VALID_ARCHS = \"ppc i386 x86_64\"");
  397. props.add (new TextPropertyComponent (plistPreprocessorDefinitions.getPropertyAsValue(), "PList Preprocessor Definitions", 2048, true),
  398. "Preprocessor definitions used during PList preprocessing (see PList Preprocess).");
  399. {
  400. static const char* cppLibNames[] = { "Use Default", "LLVM libc++", "GNU libstdc++", nullptr };
  401. static const var cppLibValues[] = { var(), "libc++", "libstdc++" };
  402. props.add (new ChoicePropertyComponent (cppStandardLibrary.getPropertyAsValue(), "C++ Library",
  403. StringArray (cppLibNames),
  404. Array<var> (cppLibValues, numElementsInArray (cppLibValues))),
  405. "The type of C++ std lib that will be linked.");
  406. }
  407. props.add (new TextWithDefaultPropertyComponent<String> (codeSignIdentity, "Code-signing Identity", 1024),
  408. "The name of a code-signing identity for Xcode to apply.");
  409. props.add (new BooleanPropertyComponent (fastMathEnabled.getPropertyAsValue(), "Relax IEEE compliance", "Enabled"),
  410. "Enable this to use FAST_MATH non-IEEE mode. (Warning: this can have unexpected results!)");
  411. props.add (new BooleanPropertyComponent (stripLocalSymbolsEnabled.getPropertyAsValue(), "Strip local symbols", "Enabled"),
  412. "Enable this to strip any locally defined symbols resulting in a smaller binary size. Enabling this "
  413. "will also remove any function names from crash logs. Must be disabled for static library projects.");
  414. }
  415. String getModuleLibraryArchName() const override
  416. {
  417. return "${CURRENT_ARCH}";
  418. }
  419. private:
  420. //==========================================================================
  421. void addXcodePluginInstallPathProperties (PropertyListBuilder& props)
  422. {
  423. auto isBuildingAnyPlugins = (project.shouldBuildVST() || project.shouldBuildVST3() || project.shouldBuildAU()
  424. || project.shouldBuildRTAS() || project.shouldBuildAAX());
  425. if (isBuildingAnyPlugins)
  426. props.add (new BooleanPropertyComponent (pluginBinaryCopyStepEnabled.getPropertyAsValue(),
  427. "Enable Plugin Copy Step", "Enabled"),
  428. "Enable this to copy plugin binaries to the specified folder after building.");
  429. if (project.shouldBuildVST())
  430. props.add (new TextWithDefaultPropertyComponentWithEnablement (vstBinaryLocation, pluginBinaryCopyStepEnabled.getPropertyAsValue(),
  431. "VST Binary Location", 1024),
  432. "The folder in which the compiled VST binary should be placed.");
  433. if (project.shouldBuildVST3())
  434. props.add (new TextWithDefaultPropertyComponentWithEnablement (vst3BinaryLocation, pluginBinaryCopyStepEnabled.getPropertyAsValue(),
  435. "VST3 Binary Location", 1024),
  436. "The folder in which the compiled VST3 binary should be placed.");
  437. if (project.shouldBuildAU())
  438. props.add (new TextWithDefaultPropertyComponentWithEnablement (auBinaryLocation, pluginBinaryCopyStepEnabled.getPropertyAsValue(),
  439. "AU Binary Location", 1024),
  440. "The folder in which the compiled AU binary should be placed.");
  441. if (project.shouldBuildRTAS())
  442. props.add (new TextWithDefaultPropertyComponentWithEnablement (rtasBinaryLocation, pluginBinaryCopyStepEnabled.getPropertyAsValue(),
  443. "RTAS Binary Location", 1024),
  444. "The folder in which the compiled RTAS binary should be placed.");
  445. if (project.shouldBuildAAX())
  446. props.add (new TextWithDefaultPropertyComponentWithEnablement (aaxBinaryLocation, pluginBinaryCopyStepEnabled.getPropertyAsValue(),
  447. "AAX Binary Location", 1024),
  448. "The folder in which the compiled AAX binary should be placed.");
  449. }
  450. void updateOldPluginBinaryLocations()
  451. {
  452. if (! config ["xcodeVstBinaryLocation"].isVoid()) vstBinaryLocation = config ["xcodeVstBinaryLocation"];
  453. if (! config ["xcodeVst3BinaryLocation"].isVoid()) vst3BinaryLocation = config ["xcodeVst3BinaryLocation"];
  454. if (! config ["xcodeAudioUnitBinaryLocation"].isVoid()) auBinaryLocation = config ["xcodeAudioUnitBinaryLocation"];
  455. if (! config ["xcodeRtasBinaryLocation"].isVoid()) rtasBinaryLocation = config ["xcodeRtasBinaryLocation"];
  456. if (! config ["xcodeAaxBinaryLocation"].isVoid()) aaxBinaryLocation = config ["xcodeAaxBinaryLocation"];
  457. }
  458. };
  459. BuildConfiguration::Ptr createBuildConfig (const ValueTree& v) const override
  460. {
  461. return new XcodeBuildConfiguration (project, v, iOS, *this);
  462. }
  463. public:
  464. //==============================================================================
  465. /* The numbers for these enum values are defined by Xcode for the different
  466. possible destinations of a "copy files" post-build step.
  467. */
  468. enum XcodeCopyFilesDestinationIDs
  469. {
  470. kWrapperFolder = 1,
  471. kExecutablesFolder = 6,
  472. kResourcesFolder = 7,
  473. kFrameworksFolder = 10,
  474. kSharedFrameworksFolder = 11,
  475. kSharedSupportFolder = 12,
  476. kPluginsFolder = 13,
  477. kJavaResourcesFolder = 15,
  478. kXPCServicesFolder = 16
  479. };
  480. //==============================================================================
  481. struct XcodeTarget : ProjectType::Target
  482. {
  483. //==============================================================================
  484. XcodeTarget (ProjectType::Target::Type targetType, const XcodeProjectExporter& exporter)
  485. : ProjectType::Target (targetType),
  486. owner (exporter)
  487. {
  488. switch (type)
  489. {
  490. case GUIApp:
  491. xcodePackageType = "APPL";
  492. xcodeBundleSignature = "????";
  493. xcodeFileType = "wrapper.application";
  494. xcodeBundleExtension = ".app";
  495. xcodeProductType = "com.apple.product-type.application";
  496. xcodeCopyToProductInstallPathAfterBuild = false;
  497. break;
  498. case ConsoleApp:
  499. xcodeFileType = "compiled.mach-o.executable";
  500. xcodeBundleExtension = String();
  501. xcodeProductType = "com.apple.product-type.tool";
  502. xcodeCopyToProductInstallPathAfterBuild = false;
  503. break;
  504. case StaticLibrary:
  505. xcodeFileType = "archive.ar";
  506. xcodeBundleExtension = ".a";
  507. xcodeProductType = "com.apple.product-type.library.static";
  508. xcodeCopyToProductInstallPathAfterBuild = false;
  509. break;
  510. case DynamicLibrary:
  511. xcodeFileType = "compiled.mach-o.dylib";
  512. xcodeProductType = "com.apple.product-type.library.dynamic";
  513. xcodeBundleExtension = ".dylib";
  514. xcodeCopyToProductInstallPathAfterBuild = false;
  515. break;
  516. case VSTPlugIn:
  517. xcodePackageType = "BNDL";
  518. xcodeBundleSignature = "????";
  519. xcodeFileType = "wrapper.cfbundle";
  520. xcodeBundleExtension = ".vst";
  521. xcodeProductType = "com.apple.product-type.bundle";
  522. xcodeCopyToProductInstallPathAfterBuild = true;
  523. break;
  524. case VST3PlugIn:
  525. xcodePackageType = "BNDL";
  526. xcodeBundleSignature = "????";
  527. xcodeFileType = "wrapper.cfbundle";
  528. xcodeBundleExtension = ".vst3";
  529. xcodeProductType = "com.apple.product-type.bundle";
  530. xcodeCopyToProductInstallPathAfterBuild = true;
  531. break;
  532. case AudioUnitPlugIn:
  533. xcodePackageType = "BNDL";
  534. xcodeBundleSignature = "????";
  535. xcodeFileType = "wrapper.cfbundle";
  536. xcodeBundleExtension = ".component";
  537. xcodeProductType = "com.apple.product-type.bundle";
  538. xcodeCopyToProductInstallPathAfterBuild = true;
  539. addExtraAudioUnitTargetSettings();
  540. break;
  541. case StandalonePlugIn:
  542. xcodePackageType = "APPL";
  543. xcodeBundleSignature = "????";
  544. xcodeFileType = "wrapper.application";
  545. xcodeBundleExtension = ".app";
  546. xcodeProductType = "com.apple.product-type.application";
  547. xcodeCopyToProductInstallPathAfterBuild = false;
  548. break;
  549. case AudioUnitv3PlugIn:
  550. xcodePackageType = "XPC!";
  551. xcodeBundleSignature = "????";
  552. xcodeFileType = "wrapper.app-extension";
  553. xcodeBundleExtension = ".appex";
  554. xcodeBundleIDSubPath = "AUv3";
  555. xcodeProductType = "com.apple.product-type.app-extension";
  556. xcodeCopyToProductInstallPathAfterBuild = false;
  557. addExtraAudioUnitv3PlugInTargetSettings();
  558. break;
  559. case AAXPlugIn:
  560. xcodePackageType = "TDMw";
  561. xcodeBundleSignature = "PTul";
  562. xcodeFileType = "wrapper.cfbundle";
  563. xcodeBundleExtension = ".aaxplugin";
  564. xcodeProductType = "com.apple.product-type.bundle";
  565. xcodeCopyToProductInstallPathAfterBuild = true;
  566. break;
  567. case RTASPlugIn:
  568. xcodePackageType = "TDMw";
  569. xcodeBundleSignature = "PTul";
  570. xcodeFileType = "wrapper.cfbundle";
  571. xcodeBundleExtension = ".dpm";
  572. xcodeProductType = "com.apple.product-type.bundle";
  573. xcodeCopyToProductInstallPathAfterBuild = true;
  574. break;
  575. case SharedCodeTarget:
  576. xcodeFileType = "archive.ar";
  577. xcodeBundleExtension = ".a";
  578. xcodeProductType = "com.apple.product-type.library.static";
  579. xcodeCopyToProductInstallPathAfterBuild = false;
  580. break;
  581. case AggregateTarget:
  582. xcodeCopyToProductInstallPathAfterBuild = false;
  583. break;
  584. default:
  585. // unknown target type!
  586. jassertfalse;
  587. break;
  588. }
  589. }
  590. String getXcodeSchemeName() const
  591. {
  592. return owner.projectName + " - " + getName();
  593. }
  594. String getID() const
  595. {
  596. return owner.createID (String ("__target") + getName());
  597. }
  598. String getInfoPlistName() const
  599. {
  600. return String ("Info-") + String (getName()).replace (" ", "_") + String (".plist");
  601. }
  602. String xcodePackageType, xcodeBundleSignature, xcodeBundleExtension;
  603. String xcodeProductType, xcodeFileType;
  604. String xcodeOtherRezFlags, xcodeBundleIDSubPath;
  605. bool xcodeCopyToProductInstallPathAfterBuild;
  606. StringArray xcodeFrameworks, xcodeLibs;
  607. Array<XmlElement> xcodeExtraPListEntries;
  608. StringArray frameworkIDs, buildPhaseIDs, configIDs, sourceIDs, rezFileIDs;
  609. StringArray frameworkNames;
  610. String dependencyID, mainBuildProductID;
  611. File infoPlistFile;
  612. struct SourceFileInfo
  613. {
  614. RelativePath path;
  615. bool shouldBeCompiled = false;
  616. };
  617. Array<SourceFileInfo> getSourceFilesInfo (const Project::Item& projectItem) const
  618. {
  619. Array<SourceFileInfo> result;
  620. const Type targetType = (owner.getProject().getProjectType().isAudioPlugin() ? type : SharedCodeTarget);
  621. if (projectItem.isGroup())
  622. {
  623. for (int i = 0; i < projectItem.getNumChildren(); ++i)
  624. result.addArray (getSourceFilesInfo (projectItem.getChild (i)));
  625. }
  626. else if (projectItem.shouldBeAddedToTargetProject()
  627. && owner.getProject().getTargetTypeFromFilePath (projectItem.getFile(), true) == targetType)
  628. {
  629. SourceFileInfo info;
  630. info.path = RelativePath (projectItem.getFile(), owner.getTargetFolder(), RelativePath::buildTargetFolder);
  631. jassert (info.path.getRoot() == RelativePath::buildTargetFolder);
  632. if (targetType == SharedCodeTarget || projectItem.shouldBeCompiled())
  633. info.shouldBeCompiled = projectItem.shouldBeCompiled();
  634. result.add (info);
  635. }
  636. return result;
  637. }
  638. //==============================================================================
  639. void addMainBuildProduct() const
  640. {
  641. jassert (xcodeFileType.isNotEmpty());
  642. jassert (xcodeBundleExtension.isEmpty() || xcodeBundleExtension.startsWithChar ('.'));
  643. if (ProjectExporter::BuildConfiguration::Ptr config = owner.getConfiguration(0))
  644. {
  645. String productName (owner.replacePreprocessorTokens (*config, config->getTargetBinaryNameString()));
  646. if (xcodeFileType == "archive.ar")
  647. productName = getStaticLibbedFilename (productName);
  648. else
  649. productName += xcodeBundleExtension;
  650. addBuildProduct (xcodeFileType, productName);
  651. }
  652. }
  653. //==============================================================================
  654. void addBuildProduct (const String& fileType, const String& binaryName) const
  655. {
  656. ValueTree* v = new ValueTree (owner.createID (String ("__productFileID") + getName()));
  657. v->setProperty ("isa", "PBXFileReference", nullptr);
  658. v->setProperty ("explicitFileType", fileType, nullptr);
  659. v->setProperty ("includeInIndex", (int) 0, nullptr);
  660. v->setProperty ("path", sanitisePath (binaryName), nullptr);
  661. v->setProperty ("sourceTree", "BUILT_PRODUCTS_DIR", nullptr);
  662. owner.pbxFileReferences.add (v);
  663. }
  664. //==============================================================================
  665. void addDependency()
  666. {
  667. jassert (dependencyID.isEmpty());
  668. dependencyID = owner.createID (String ("__dependency") + getName());
  669. ValueTree* const v = new ValueTree (dependencyID);
  670. v->setProperty ("isa", "PBXTargetDependency", nullptr);
  671. v->setProperty ("target", getID(), nullptr);
  672. owner.misc.add (v);
  673. }
  674. String getDependencyID() const
  675. {
  676. jassert (dependencyID.isNotEmpty());
  677. return dependencyID;
  678. }
  679. //==============================================================================
  680. void addTargetConfig (const String& configName, const StringArray& buildSettings)
  681. {
  682. String configID = owner.createID (String ("targetconfigid_") + getName() + String ("_") + configName);
  683. ValueTree* v = new ValueTree (configID);
  684. v->setProperty ("isa", "XCBuildConfiguration", nullptr);
  685. v->setProperty ("buildSettings", indentBracedList (buildSettings), nullptr);
  686. v->setProperty (Ids::name, configName, nullptr);
  687. configIDs.add (configID);
  688. owner.targetConfigs.add (v);
  689. }
  690. //==============================================================================
  691. String getTargetAttributes() const
  692. {
  693. auto attributes = getID() + " = { ";
  694. auto developmentTeamID = owner.getIosDevelopmentTeamIDString();
  695. if (developmentTeamID.isNotEmpty())
  696. {
  697. attributes << "DevelopmentTeam = " << developmentTeamID << "; ";
  698. attributes << "ProvisioningStyle = Automatic; ";
  699. }
  700. auto appGroupsEnabled = (owner.iOS && owner.isAppGroupsEnabled() ? 1 : 0);
  701. auto inAppPurchasesEnabled = owner.isInAppPurchasesEnabled() ? 1 : 0;
  702. auto interAppAudioEnabled = (owner.iOS
  703. && type == Target::StandalonePlugIn
  704. && owner.getProject().shouldEnableIAA()) ? 1 : 0;
  705. auto pushNotificationsEnabled = owner.isPushNotificationsEnabled() ? 1 : 0;
  706. auto sandboxEnabled = (type == Target::AudioUnitv3PlugIn ? 1 : 0);
  707. attributes << "SystemCapabilities = {";
  708. attributes << "com.apple.ApplicationGroups.iOS = { enabled = " << appGroupsEnabled << "; }; ";
  709. attributes << "com.apple.InAppPurchase = { enabled = " << inAppPurchasesEnabled << "; }; ";
  710. attributes << "com.apple.InterAppAudio = { enabled = " << interAppAudioEnabled << "; }; ";
  711. attributes << "com.apple.Push = { enabled = " << pushNotificationsEnabled << "; }; ";
  712. attributes << "com.apple.Sandbox = { enabled = " << sandboxEnabled << "; }; ";
  713. if (owner.iOS && owner.isiCloudPermissionsEnabled())
  714. attributes << "com.apple.iCloud = { enabled = 1; }; ";
  715. attributes << "}; };";
  716. return attributes;
  717. }
  718. //==============================================================================
  719. ValueTree& addBuildPhase (const String& buildPhaseType, const StringArray& fileIds, const StringRef humanReadableName = StringRef())
  720. {
  721. String buildPhaseName = buildPhaseType + String ("_") + getName() + String ("_") + (humanReadableName.isNotEmpty() ? String (humanReadableName) : String ("resbuildphase"));
  722. String buildPhaseId (owner.createID (buildPhaseName));
  723. int n = 0;
  724. while (buildPhaseIDs.contains (buildPhaseId))
  725. buildPhaseId = owner.createID (buildPhaseName + String (++n));
  726. buildPhaseIDs.add (buildPhaseId);
  727. ValueTree* v = new ValueTree (buildPhaseId);
  728. v->setProperty ("isa", buildPhaseType, nullptr);
  729. v->setProperty ("buildActionMask", "2147483647", nullptr);
  730. v->setProperty ("files", indentParenthesisedList (fileIds), nullptr);
  731. v->setProperty ("runOnlyForDeploymentPostprocessing", (int) 0, nullptr);
  732. if (humanReadableName.isNotEmpty())
  733. v->setProperty ("name", String (humanReadableName), nullptr);
  734. owner.misc.add (v);
  735. return *v;
  736. }
  737. bool shouldCreatePList() const
  738. {
  739. const ProjectType::Target::TargetFileType fileType = getTargetFileType();
  740. return (fileType == executable && type != ConsoleApp) || fileType == pluginBundle || fileType == macOSAppex;
  741. }
  742. //==============================================================================
  743. bool shouldAddEntitlements() const
  744. {
  745. if (owner.isPushNotificationsEnabled() || owner.isAppGroupsEnabled() || (owner.isiOS() && owner.isiCloudPermissionsEnabled()))
  746. return true;
  747. if (owner.project.getProjectType().isAudioPlugin()
  748. && ( (owner.isOSX() && type == Target::AudioUnitv3PlugIn)
  749. || (owner.isiOS() && type == Target::StandalonePlugIn && owner.getProject().shouldEnableIAA())))
  750. return true;
  751. return false;
  752. }
  753. String getBundleIdentifier() const
  754. {
  755. String bundleIdentifier = owner.project.getBundleIdentifier().toString();
  756. if (xcodeBundleIDSubPath.isNotEmpty())
  757. {
  758. StringArray bundleIdSegments = StringArray::fromTokens (bundleIdentifier, ".", StringRef());
  759. jassert (bundleIdSegments.size() > 0);
  760. bundleIdentifier += String (".") + bundleIdSegments[bundleIdSegments.size() - 1] + xcodeBundleIDSubPath;
  761. }
  762. return bundleIdentifier;
  763. }
  764. //==============================================================================
  765. StringPairArray getTargetSettings (const XcodeBuildConfiguration& config) const
  766. {
  767. StringPairArray s;
  768. if (type == AggregateTarget && ! owner.isiOS())
  769. {
  770. // the aggregate target needs to have the deployment target set for
  771. // pre-/post-build scripts
  772. String sdkRoot;
  773. s.set ("MACOSX_DEPLOYMENT_TARGET", getOSXDeploymentTarget (config, &sdkRoot));
  774. if (sdkRoot.isNotEmpty())
  775. s.set ("SDKROOT", sdkRoot);
  776. return s;
  777. }
  778. s.set ("PRODUCT_BUNDLE_IDENTIFIER", getBundleIdentifier());
  779. const String arch ((! owner.isiOS() && type == Target::AudioUnitv3PlugIn) ? osxArch_64Bit : config.osxArchitecture.get());
  780. if (arch == osxArch_Native) s.set ("ARCHS", "\"$(NATIVE_ARCH_ACTUAL)\"");
  781. else if (arch == osxArch_32BitUniversal) s.set ("ARCHS", "\"$(ARCHS_STANDARD_32_BIT)\"");
  782. else if (arch == osxArch_64BitUniversal) s.set ("ARCHS", "\"$(ARCHS_STANDARD_32_64_BIT)\"");
  783. else if (arch == osxArch_64Bit) s.set ("ARCHS", "\"$(ARCHS_STANDARD_64_BIT)\"");
  784. s.set ("HEADER_SEARCH_PATHS", String ("(") + getHeaderSearchPaths (config).joinIntoString (", ") + ", \"$(inherited)\")");
  785. s.set ("USE_HEADERMAP", String (static_cast<bool> (config.exporter.settings.getProperty ("useHeaderMap")) ? "YES" : "NO"));
  786. s.set ("GCC_OPTIMIZATION_LEVEL", config.getGCCOptimisationFlag());
  787. if (shouldCreatePList())
  788. {
  789. s.set ("INFOPLIST_FILE", infoPlistFile.getFileName());
  790. if (owner.getPListPrefixHeaderString().isNotEmpty())
  791. s.set ("INFOPLIST_PREFIX_HEADER", owner.getPListPrefixHeaderString());
  792. s.set ("INFOPLIST_PREPROCESS", (owner.isPListPreprocessEnabled() ? String ("YES") : String ("NO")));
  793. auto plistDefs = parsePreprocessorDefs (config.plistPreprocessorDefinitions.get());
  794. StringArray defsList;
  795. for (int i = 0; i < plistDefs.size(); ++i)
  796. {
  797. String def (plistDefs.getAllKeys()[i]);
  798. const String value (plistDefs.getAllValues()[i]);
  799. if (value.isNotEmpty())
  800. def << "=" << value.replace ("\"", "\\\\\\\"");
  801. defsList.add ("\"" + def + "\"");
  802. }
  803. if (defsList.size() > 0)
  804. s.set ("INFOPLIST_PREPROCESSOR_DEFINITIONS", indentParenthesisedList (defsList));
  805. }
  806. if (config.isLinkTimeOptimisationEnabled())
  807. s.set ("LLVM_LTO", "YES");
  808. if (config.fastMathEnabled.get())
  809. s.set ("GCC_FAST_MATH", "YES");
  810. const String extraFlags (owner.replacePreprocessorTokens (config, owner.getExtraCompilerFlagsString()).trim());
  811. if (extraFlags.isNotEmpty())
  812. s.set ("OTHER_CPLUSPLUSFLAGS", extraFlags.quoted());
  813. String installPath = getInstallPathForConfiguration (config);
  814. if (installPath.isNotEmpty())
  815. {
  816. s.set ("INSTALL_PATH", installPath.quoted());
  817. if (xcodeCopyToProductInstallPathAfterBuild)
  818. {
  819. s.set ("DEPLOYMENT_LOCATION", "YES");
  820. s.set ("DSTROOT", "/");
  821. }
  822. }
  823. if (getTargetFileType() == pluginBundle)
  824. {
  825. s.set ("LIBRARY_STYLE", "Bundle");
  826. s.set ("WRAPPER_EXTENSION", xcodeBundleExtension.substring (1));
  827. s.set ("GENERATE_PKGINFO_FILE", "YES");
  828. }
  829. if (xcodeOtherRezFlags.isNotEmpty())
  830. s.set ("OTHER_REZFLAGS", "\"" + xcodeOtherRezFlags + "\"");
  831. String configurationBuildDir = "$(PROJECT_DIR)/build/$(CONFIGURATION)";
  832. if (config.getTargetBinaryRelativePathString().isNotEmpty())
  833. {
  834. // a target's position can either be defined via installPath + xcodeCopyToProductInstallPathAfterBuild
  835. // (= for audio plug-ins) or using a custom binary path (for everything else), but not both (= conflict!)
  836. jassert (! xcodeCopyToProductInstallPathAfterBuild);
  837. RelativePath binaryPath (config.getTargetBinaryRelativePathString(), RelativePath::projectFolder);
  838. configurationBuildDir = sanitisePath (binaryPath.rebased (owner.projectFolder, owner.getTargetFolder(), RelativePath::buildTargetFolder)
  839. .toUnixStyle());
  840. }
  841. s.set ("CONFIGURATION_BUILD_DIR", addQuotesIfRequired (configurationBuildDir));
  842. String gccVersion ("com.apple.compilers.llvm.clang.1_0");
  843. if (owner.iOS)
  844. {
  845. s.set ("ASSETCATALOG_COMPILER_APPICON_NAME", "AppIcon");
  846. s.set ("ASSETCATALOG_COMPILER_LAUNCHIMAGE_NAME", "LaunchImage");
  847. }
  848. else
  849. {
  850. String sdkRoot;
  851. s.set ("MACOSX_DEPLOYMENT_TARGET", getOSXDeploymentTarget (config, &sdkRoot));
  852. if (sdkRoot.isNotEmpty())
  853. s.set ("SDKROOT", sdkRoot);
  854. s.set ("MACOSX_DEPLOYMENT_TARGET_ppc", "10.4");
  855. s.set ("SDKROOT_ppc", "macosx10.5");
  856. }
  857. s.set ("GCC_VERSION", gccVersion);
  858. s.set ("CLANG_LINK_OBJC_RUNTIME", "NO");
  859. if (isUsingCodeSigning (config))
  860. {
  861. s.set (owner.iOS ? "\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\"" : "CODE_SIGN_IDENTITY",
  862. getCodeSignIdentity (config).quoted());
  863. s.set ("PROVISIONING_PROFILE_SPECIFIER", "\"\"");
  864. }
  865. if (owner.getIosDevelopmentTeamIDString().isNotEmpty())
  866. s.set ("DEVELOPMENT_TEAM", owner.getIosDevelopmentTeamIDString());
  867. if (shouldAddEntitlements())
  868. s.set ("CODE_SIGN_ENTITLEMENTS", owner.getEntitlementsFileName().quoted());
  869. {
  870. auto cppStandard = owner.project.getCppStandardValue().toString();
  871. if (cppStandard == "latest")
  872. cppStandard = "1z";
  873. s.set ("CLANG_CXX_LANGUAGE_STANDARD", (String (owner.shouldUseGNUExtensions() ? "gnu++"
  874. : "c++") + cppStandard).quoted());
  875. }
  876. if (config.cppStandardLibrary.get().isNotEmpty())
  877. s.set ("CLANG_CXX_LIBRARY", config.cppStandardLibrary.get().quoted());
  878. s.set ("COMBINE_HIDPI_IMAGES", "YES");
  879. {
  880. StringArray linkerFlags, librarySearchPaths;
  881. getLinkerSettings (config, linkerFlags, librarySearchPaths);
  882. if (linkerFlags.size() > 0)
  883. s.set ("OTHER_LDFLAGS", linkerFlags.joinIntoString (" ").quoted());
  884. librarySearchPaths.addArray (config.getLibrarySearchPaths());
  885. librarySearchPaths = getCleanedStringArray (librarySearchPaths);
  886. if (librarySearchPaths.size() > 0)
  887. {
  888. String libPaths ("(\"$(inherited)\"");
  889. for (auto& p : librarySearchPaths)
  890. libPaths += ", \"\\\"" + p + "\\\"\"";
  891. s.set ("LIBRARY_SEARCH_PATHS", libPaths + ")");
  892. }
  893. }
  894. StringPairArray defines;
  895. if (config.isDebug())
  896. {
  897. defines.set ("_DEBUG", "1");
  898. defines.set ("DEBUG", "1");
  899. s.set ("COPY_PHASE_STRIP", "NO");
  900. s.set ("GCC_DYNAMIC_NO_PIC", "NO");
  901. }
  902. else
  903. {
  904. defines.set ("_NDEBUG", "1");
  905. defines.set ("NDEBUG", "1");
  906. s.set ("GCC_GENERATE_DEBUGGING_SYMBOLS", "NO");
  907. s.set ("GCC_SYMBOLS_PRIVATE_EXTERN", "YES");
  908. s.set ("DEAD_CODE_STRIPPING", "YES");
  909. }
  910. if (type != Target::SharedCodeTarget && type != Target::StaticLibrary && type != Target::DynamicLibrary
  911. && config.stripLocalSymbolsEnabled.get())
  912. {
  913. s.set ("STRIPFLAGS", "\"-x\"");
  914. s.set ("DEPLOYMENT_POSTPROCESSING", "YES");
  915. s.set ("SEPARATE_STRIP", "YES");
  916. }
  917. if (owner.isInAppPurchasesEnabled())
  918. defines.set ("JUCE_IN_APP_PURCHASES", "1");
  919. if (owner.isPushNotificationsEnabled())
  920. defines.set ("JUCE_PUSH_NOTIFICATIONS", "1");
  921. defines = mergePreprocessorDefs (defines, owner.getAllPreprocessorDefs (config, type));
  922. StringArray defsList;
  923. for (int i = 0; i < defines.size(); ++i)
  924. {
  925. String def (defines.getAllKeys()[i]);
  926. const String value (defines.getAllValues()[i]);
  927. if (value.isNotEmpty())
  928. def << "=" << value.replace ("\"", "\\\\\\\"");
  929. defsList.add ("\"" + def + "\"");
  930. }
  931. s.set ("GCC_PREPROCESSOR_DEFINITIONS", indentParenthesisedList (defsList));
  932. StringArray customFlags;
  933. customFlags.addTokens (config.customXcodeFlags.get(), ",", "\"'");
  934. customFlags.removeEmptyStrings();
  935. for (auto flag : customFlags)
  936. {
  937. s.set (flag.upToFirstOccurrenceOf ("=", false, false).trim(),
  938. flag.fromFirstOccurrenceOf ("=", false, false).trim().quoted());
  939. }
  940. return s;
  941. }
  942. String getInstallPathForConfiguration (const XcodeBuildConfiguration& config) const
  943. {
  944. switch (type)
  945. {
  946. case GUIApp: return "$(HOME)/Applications";
  947. case ConsoleApp: return "/usr/bin";
  948. case VSTPlugIn: return config.pluginBinaryCopyStepEnabled.get() ? config.vstBinaryLocation.get() : String();
  949. case VST3PlugIn: return config.pluginBinaryCopyStepEnabled.get() ? config.vst3BinaryLocation.get() : String();
  950. case AudioUnitPlugIn: return config.pluginBinaryCopyStepEnabled.get() ? config.auBinaryLocation.get() : String();
  951. case RTASPlugIn: return config.pluginBinaryCopyStepEnabled.get() ? config.rtasBinaryLocation.get() : String();
  952. case AAXPlugIn: return config.pluginBinaryCopyStepEnabled.get() ? config.aaxBinaryLocation.get() : String();
  953. case SharedCodeTarget: return owner.isiOS() ? "@executable_path/Frameworks" : "@executable_path/../Frameworks";
  954. default: return {};
  955. }
  956. }
  957. //==============================================================================
  958. void getLinkerSettings (const BuildConfiguration& config, StringArray& flags, StringArray& librarySearchPaths) const
  959. {
  960. if (getTargetFileType() == pluginBundle)
  961. flags.add (owner.isiOS() ? "-bitcode_bundle" : "-bundle");
  962. Array<RelativePath> extraLibs;
  963. addExtraLibsForTargetType (config, extraLibs);
  964. for (auto& lib : extraLibs)
  965. {
  966. flags.add (getLinkerFlagForLib (lib.getFileNameWithoutExtension()));
  967. librarySearchPaths.add (owner.getSearchPathForStaticLibrary (lib));
  968. }
  969. if (owner.project.getProjectType().isAudioPlugin() && type != Target::SharedCodeTarget)
  970. {
  971. if (owner.getTargetOfType (Target::SharedCodeTarget) != nullptr)
  972. {
  973. String productName (getStaticLibbedFilename (owner.replacePreprocessorTokens (config, config.getTargetBinaryNameString())));
  974. RelativePath sharedCodelib (productName, RelativePath::buildTargetFolder);
  975. flags.add (getLinkerFlagForLib (sharedCodelib.getFileNameWithoutExtension()));
  976. }
  977. }
  978. flags.add (owner.replacePreprocessorTokens (config, owner.getExtraLinkerFlagsString()));
  979. flags.add (owner.getExternalLibraryFlags (config));
  980. StringArray libs (owner.xcodeLibs);
  981. libs.addArray (xcodeLibs);
  982. for (auto& l : libs)
  983. flags.add (getLinkerFlagForLib (l));
  984. flags = getCleanedStringArray (flags);
  985. }
  986. //========================================================================== c
  987. void writeInfoPlistFile() const
  988. {
  989. if (! shouldCreatePList())
  990. return;
  991. ScopedPointer<XmlElement> plist (XmlDocument::parse (owner.getPListToMergeString()));
  992. if (plist == nullptr || ! plist->hasTagName ("plist"))
  993. plist = new XmlElement ("plist");
  994. XmlElement* dict = plist->getChildByName ("dict");
  995. if (dict == nullptr)
  996. dict = plist->createNewChildElement ("dict");
  997. if (owner.iOS)
  998. {
  999. addPlistDictionaryKeyBool (dict, "LSRequiresIPhoneOS", true);
  1000. if (owner.isMicrophonePermissionEnabled())
  1001. addPlistDictionaryKey (dict, "NSMicrophoneUsageDescription", "This app requires microphone input.");
  1002. if (type != AudioUnitv3PlugIn)
  1003. addPlistDictionaryKeyBool (dict, "UIViewControllerBasedStatusBarAppearance", false);
  1004. }
  1005. addPlistDictionaryKey (dict, "CFBundleExecutable", "${EXECUTABLE_NAME}");
  1006. if (! owner.iOS) // (NB: on iOS this causes error ITMS-90032 during publishing)
  1007. addPlistDictionaryKey (dict, "CFBundleIconFile", owner.iconFile.exists() ? owner.iconFile.getFileName() : String());
  1008. addPlistDictionaryKey (dict, "CFBundleIdentifier", getBundleIdentifier());
  1009. addPlistDictionaryKey (dict, "CFBundleName", owner.projectName);
  1010. // needed by NSExtension on iOS
  1011. addPlistDictionaryKey (dict, "CFBundleDisplayName", owner.projectName);
  1012. addPlistDictionaryKey (dict, "CFBundlePackageType", xcodePackageType);
  1013. addPlistDictionaryKey (dict, "CFBundleSignature", xcodeBundleSignature);
  1014. addPlistDictionaryKey (dict, "CFBundleShortVersionString", owner.project.getVersionString());
  1015. addPlistDictionaryKey (dict, "CFBundleVersion", owner.project.getVersionString());
  1016. addPlistDictionaryKey (dict, "NSHumanReadableCopyright", owner.project.getCompanyCopyright().toString());
  1017. addPlistDictionaryKeyBool (dict, "NSHighResolutionCapable", true);
  1018. StringArray documentExtensions;
  1019. documentExtensions.addTokens (replacePreprocessorDefs (owner.getAllPreprocessorDefs(), owner.settings ["documentExtensions"]),
  1020. ",", StringRef());
  1021. documentExtensions.trim();
  1022. documentExtensions.removeEmptyStrings (true);
  1023. if (documentExtensions.size() > 0 && type != AudioUnitv3PlugIn)
  1024. {
  1025. dict->createNewChildElement ("key")->addTextElement ("CFBundleDocumentTypes");
  1026. XmlElement* dict2 = dict->createNewChildElement ("array")->createNewChildElement ("dict");
  1027. XmlElement* arrayTag = nullptr;
  1028. for (String ex : documentExtensions)
  1029. {
  1030. if (ex.startsWithChar ('.'))
  1031. ex = ex.substring (1);
  1032. if (arrayTag == nullptr)
  1033. {
  1034. dict2->createNewChildElement ("key")->addTextElement ("CFBundleTypeExtensions");
  1035. arrayTag = dict2->createNewChildElement ("array");
  1036. addPlistDictionaryKey (dict2, "CFBundleTypeName", ex);
  1037. addPlistDictionaryKey (dict2, "CFBundleTypeRole", "Editor");
  1038. addPlistDictionaryKey (dict2, "CFBundleTypeIconFile", "Icon");
  1039. addPlistDictionaryKey (dict2, "NSPersistentStoreTypeKey", "XML");
  1040. }
  1041. arrayTag->createNewChildElement ("string")->addTextElement (ex);
  1042. }
  1043. }
  1044. if (owner.settings ["UIFileSharingEnabled"] && type != AudioUnitv3PlugIn)
  1045. addPlistDictionaryKeyBool (dict, "UIFileSharingEnabled", true);
  1046. if (owner.settings ["UISupportsDocumentBrowser"])
  1047. addPlistDictionaryKeyBool (dict, "UISupportsDocumentBrowser", true);
  1048. if (owner.settings ["UIStatusBarHidden"] && type != AudioUnitv3PlugIn)
  1049. addPlistDictionaryKeyBool (dict, "UIStatusBarHidden", true);
  1050. if (owner.iOS)
  1051. {
  1052. if (type != AudioUnitv3PlugIn)
  1053. {
  1054. // Forcing full screen disables the split screen feature and prevents error ITMS-90475
  1055. addPlistDictionaryKeyBool (dict, "UIRequiresFullScreen", true);
  1056. addPlistDictionaryKeyBool (dict, "UIStatusBarHidden", true);
  1057. addIosScreenOrientations (dict);
  1058. addIosBackgroundModes (dict);
  1059. }
  1060. if (type == StandalonePlugIn && owner.getProject().shouldEnableIAA())
  1061. {
  1062. XmlElement audioComponentsPlistKey ("key");
  1063. audioComponentsPlistKey.addTextElement ("AudioComponents");
  1064. dict->addChildElement (new XmlElement (audioComponentsPlistKey));
  1065. XmlElement audioComponentsPlistEntry ("array");
  1066. XmlElement* audioComponentsDict = audioComponentsPlistEntry.createNewChildElement ("dict");
  1067. addPlistDictionaryKey (audioComponentsDict, "name", owner.project.getIAAPluginName());
  1068. addPlistDictionaryKey (audioComponentsDict, "manufacturer", owner.project.getPluginManufacturerCode().toString().trim().substring (0, 4));
  1069. addPlistDictionaryKey (audioComponentsDict, "type", owner.project.getIAATypeCode());
  1070. addPlistDictionaryKey (audioComponentsDict, "subtype", owner.project.getPluginCode().toString().trim().substring (0, 4));
  1071. addPlistDictionaryKeyInt (audioComponentsDict, "version", owner.project.getVersionAsHexInteger());
  1072. dict->addChildElement (new XmlElement (audioComponentsPlistEntry));
  1073. }
  1074. }
  1075. for (auto& e : xcodeExtraPListEntries)
  1076. dict->addChildElement (new XmlElement (e));
  1077. MemoryOutputStream mo;
  1078. plist->writeToStream (mo, "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">");
  1079. overwriteFileIfDifferentOrThrow (infoPlistFile, mo);
  1080. }
  1081. //==============================================================================
  1082. void addIosScreenOrientations (XmlElement* dict) const
  1083. {
  1084. String screenOrientations[2] = { owner.getiPhoneScreenOrientationString(), owner.getiPadScreenOrientationString() };
  1085. String plistSuffix[2] = { "", "~ipad" };
  1086. auto orientationsAreTheSame = ( screenOrientations[0] == screenOrientations[1] );
  1087. for (int i = 0; i < (orientationsAreTheSame ? 1 : 2); ++i)
  1088. {
  1089. StringArray iOSOrientations;
  1090. if (screenOrientations[i].contains ("portrait")) { iOSOrientations.add ("UIInterfaceOrientationPortrait"); }
  1091. if (screenOrientations[i].contains ("landscape")) { iOSOrientations.add ("UIInterfaceOrientationLandscapeLeft"); iOSOrientations.add ("UIInterfaceOrientationLandscapeRight"); }
  1092. addArrayToPlist (dict, String ("UISupportedInterfaceOrientations") + plistSuffix[i], iOSOrientations);
  1093. }
  1094. }
  1095. //==============================================================================
  1096. void addIosBackgroundModes (XmlElement* dict) const
  1097. {
  1098. StringArray iosBackgroundModes;
  1099. if (owner.isBackgroundAudioEnabled()) iosBackgroundModes.add ("audio");
  1100. if (owner.isBackgroundBleEnabled()) iosBackgroundModes.add ("bluetooth-central");
  1101. if (owner.isPushNotificationsEnabled()) iosBackgroundModes.add ("remote-notification");
  1102. addArrayToPlist (dict, "UIBackgroundModes", iosBackgroundModes);
  1103. }
  1104. //==============================================================================
  1105. static void addArrayToPlist (XmlElement* dict, String arrayKey, const StringArray& arrayElements)
  1106. {
  1107. dict->createNewChildElement ("key")->addTextElement (arrayKey);
  1108. XmlElement* plistStringArray = dict->createNewChildElement ("array");
  1109. for (auto& e : arrayElements)
  1110. plistStringArray->createNewChildElement ("string")->addTextElement (e);
  1111. }
  1112. //==============================================================================
  1113. void addShellScriptBuildPhase (const String& phaseName, const String& script)
  1114. {
  1115. if (script.trim().isNotEmpty())
  1116. {
  1117. ValueTree& v = addBuildPhase ("PBXShellScriptBuildPhase", StringArray());
  1118. v.setProperty (Ids::name, phaseName, nullptr);
  1119. v.setProperty ("shellPath", "/bin/sh", nullptr);
  1120. v.setProperty ("shellScript", script.replace ("\\", "\\\\")
  1121. .replace ("\"", "\\\"")
  1122. .replace ("\r\n", "\\n")
  1123. .replace ("\n", "\\n"), nullptr);
  1124. }
  1125. }
  1126. void addCopyFilesPhase (const String& phaseName, const StringArray& files, XcodeCopyFilesDestinationIDs dst)
  1127. {
  1128. ValueTree& v = addBuildPhase ("PBXCopyFilesBuildPhase", files, phaseName);
  1129. v.setProperty ("dstPath", "", nullptr);
  1130. v.setProperty ("dstSubfolderSpec", (int) dst, nullptr);
  1131. }
  1132. //==============================================================================
  1133. StringArray getHeaderSearchPaths (const BuildConfiguration& config) const
  1134. {
  1135. StringArray paths (owner.extraSearchPaths);
  1136. paths.addArray (config.getHeaderSearchPaths());
  1137. paths.addArray (getTargetExtraHeaderSearchPaths());
  1138. if (owner.project.getModules().isModuleEnabled ("juce_audio_plugin_client"))
  1139. {
  1140. // Needed to compile .r files
  1141. paths.add (owner.getModuleFolderRelativeToProject ("juce_audio_plugin_client")
  1142. .rebased (owner.projectFolder, owner.getTargetFolder(), RelativePath::buildTargetFolder)
  1143. .toUnixStyle());
  1144. }
  1145. paths = getCleanedStringArray (paths);
  1146. for (auto& s : paths)
  1147. {
  1148. s = owner.replacePreprocessorTokens (config, s);
  1149. if (s.containsChar (' '))
  1150. s = "\"\\\"" + s + "\\\"\""; // crazy double quotes required when there are spaces..
  1151. else
  1152. s = "\"" + s + "\"";
  1153. }
  1154. return paths;
  1155. }
  1156. private:
  1157. //==============================================================================
  1158. void addExtraAudioUnitTargetSettings()
  1159. {
  1160. xcodeOtherRezFlags = "-d ppc_$ppc -d i386_$i386 -d ppc64_$ppc64 -d x86_64_$x86_64"
  1161. " -I /System/Library/Frameworks/CoreServices.framework/Frameworks/CarbonCore.framework/Versions/A/Headers"
  1162. " -I \\\"$(DEVELOPER_DIR)/Extras/CoreAudio/AudioUnits/AUPublic/AUBase\\\"";
  1163. xcodeFrameworks.addTokens ("AudioUnit CoreAudioKit", false);
  1164. XmlElement plistKey ("key");
  1165. plistKey.addTextElement ("AudioComponents");
  1166. XmlElement plistEntry ("array");
  1167. XmlElement* dict = plistEntry.createNewChildElement ("dict");
  1168. const String pluginManufacturerCode = owner.project.getPluginManufacturerCode().toString().trim().substring (0, 4);
  1169. const String pluginSubType = owner.project.getPluginCode() .toString().trim().substring (0, 4);
  1170. if (pluginManufacturerCode.toLowerCase() == pluginManufacturerCode)
  1171. {
  1172. throw SaveError ("AudioUnit plugin code identifiers invalid!\n\n"
  1173. "You have used only lower case letters in your AU plugin manufacturer identifier. "
  1174. "You must have at least one uppercase letter in your AU plugin manufacturer "
  1175. "identifier code.");
  1176. }
  1177. addPlistDictionaryKey (dict, "name", owner.project.getPluginManufacturer().toString()
  1178. + ": " + owner.project.getPluginName().toString());
  1179. addPlistDictionaryKey (dict, "description", owner.project.getPluginDesc().toString());
  1180. addPlistDictionaryKey (dict, "factoryFunction", owner.project.getPluginAUExportPrefix().toString() + "Factory");
  1181. addPlistDictionaryKey (dict, "manufacturer", pluginManufacturerCode);
  1182. addPlistDictionaryKey (dict, "type", owner.project.getAUMainTypeCode());
  1183. addPlistDictionaryKey (dict, "subtype", pluginSubType);
  1184. addPlistDictionaryKeyInt (dict, "version", owner.project.getVersionAsHexInteger());
  1185. xcodeExtraPListEntries.add (plistKey);
  1186. xcodeExtraPListEntries.add (plistEntry);
  1187. }
  1188. void addExtraAudioUnitv3PlugInTargetSettings()
  1189. {
  1190. if (owner.isiOS())
  1191. xcodeFrameworks.addTokens ("CoreAudioKit AVFoundation", false);
  1192. else
  1193. xcodeFrameworks.addTokens ("AudioUnit CoreAudioKit AVFoundation", false);
  1194. XmlElement plistKey ("key");
  1195. plistKey.addTextElement ("NSExtension");
  1196. XmlElement plistEntry ("dict");
  1197. addPlistDictionaryKey (&plistEntry, "NSExtensionPrincipalClass", owner.project.getPluginAUExportPrefix().toString() + "FactoryAUv3");
  1198. addPlistDictionaryKey (&plistEntry, "NSExtensionPointIdentifier", "com.apple.AudioUnit-UI");
  1199. plistEntry.createNewChildElement ("key")->addTextElement ("NSExtensionAttributes");
  1200. XmlElement* dict = plistEntry.createNewChildElement ("dict");
  1201. dict->createNewChildElement ("key")->addTextElement ("AudioComponents");
  1202. XmlElement* componentArray = dict->createNewChildElement ("array");
  1203. XmlElement* componentDict = componentArray->createNewChildElement ("dict");
  1204. addPlistDictionaryKey (componentDict, "name", owner.project.getPluginManufacturer().toString()
  1205. + ": " + owner.project.getPluginName().toString());
  1206. addPlistDictionaryKey (componentDict, "description", owner.project.getPluginDesc().toString());
  1207. addPlistDictionaryKey (componentDict, "factoryFunction",owner.project. getPluginAUExportPrefix().toString() + "FactoryAUv3");
  1208. addPlistDictionaryKey (componentDict, "manufacturer", owner.project.getPluginManufacturerCode().toString().trim().substring (0, 4));
  1209. addPlistDictionaryKey (componentDict, "type", owner.project.getAUMainTypeCode());
  1210. addPlistDictionaryKey (componentDict, "subtype", owner.project.getPluginCode().toString().trim().substring (0, 4));
  1211. addPlistDictionaryKeyInt (componentDict, "version", owner.project.getVersionAsHexInteger());
  1212. addPlistDictionaryKeyBool (componentDict, "sandboxSafe", true);
  1213. componentDict->createNewChildElement ("key")->addTextElement ("tags");
  1214. XmlElement* tagsArray = componentDict->createNewChildElement ("array");
  1215. tagsArray->createNewChildElement ("string")
  1216. ->addTextElement (static_cast<bool> (owner.project.getPluginIsSynth().getValue()) ? "Synth" : "Effects");
  1217. xcodeExtraPListEntries.add (plistKey);
  1218. xcodeExtraPListEntries.add (plistEntry);
  1219. }
  1220. void addExtraLibsForTargetType (const BuildConfiguration& config, Array<RelativePath>& extraLibs) const
  1221. {
  1222. if (type == AAXPlugIn)
  1223. {
  1224. auto aaxLibsFolder
  1225. = RelativePath (owner.getAAXPathValue().toString(), RelativePath::projectFolder)
  1226. .getChildFile ("Libs");
  1227. String libraryPath (config.isDebug() ? "Debug/libAAXLibrary" : "Release/libAAXLibrary");
  1228. libraryPath += (isUsingClangCppLibrary (config) ? "_libcpp.a" : ".a");
  1229. extraLibs.add (aaxLibsFolder.getChildFile (libraryPath));
  1230. }
  1231. else if (type == RTASPlugIn)
  1232. {
  1233. RelativePath rtasFolder (owner.getRTASPathValue().toString(), RelativePath::projectFolder);
  1234. extraLibs.add (rtasFolder.getChildFile ("MacBag/Libs/Debug/libPluginLibrary.a"));
  1235. extraLibs.add (rtasFolder.getChildFile ("MacBag/Libs/Release/libPluginLibrary.a"));
  1236. }
  1237. }
  1238. StringArray getTargetExtraHeaderSearchPaths() const
  1239. {
  1240. StringArray targetExtraSearchPaths;
  1241. if (type == RTASPlugIn)
  1242. {
  1243. RelativePath rtasFolder (owner.getRTASPathValue().toString(), RelativePath::projectFolder);
  1244. targetExtraSearchPaths.add ("$(DEVELOPER_DIR)/Headers/FlatCarbon");
  1245. targetExtraSearchPaths.add ("$(SDKROOT)/Developer/Headers/FlatCarbon");
  1246. static const char* p[] = { "AlturaPorts/TDMPlugIns/PlugInLibrary/Controls",
  1247. "AlturaPorts/TDMPlugIns/PlugInLibrary/CoreClasses",
  1248. "AlturaPorts/TDMPlugIns/PlugInLibrary/DSPClasses",
  1249. "AlturaPorts/TDMPlugIns/PlugInLibrary/EffectClasses",
  1250. "AlturaPorts/TDMPlugIns/PlugInLibrary/MacBuild",
  1251. "AlturaPorts/TDMPlugIns/PlugInLibrary/Meters",
  1252. "AlturaPorts/TDMPlugIns/PlugInLibrary/ProcessClasses",
  1253. "AlturaPorts/TDMPlugIns/PlugInLibrary/ProcessClasses/Interfaces",
  1254. "AlturaPorts/TDMPlugIns/PlugInLibrary/RTASP_Adapt",
  1255. "AlturaPorts/TDMPlugIns/PlugInLibrary/Utilities",
  1256. "AlturaPorts/TDMPlugIns/PlugInLibrary/ViewClasses",
  1257. "AlturaPorts/TDMPlugIns/DSPManager/**",
  1258. "AlturaPorts/TDMPlugIns/SupplementalPlugInLib/Encryption",
  1259. "AlturaPorts/TDMPlugIns/SupplementalPlugInLib/GraphicsExtensions",
  1260. "AlturaPorts/TDMPlugIns/common/**",
  1261. "AlturaPorts/TDMPlugIns/common/PI_LibInterface",
  1262. "AlturaPorts/TDMPlugIns/PACEProtection/**",
  1263. "AlturaPorts/TDMPlugIns/SignalProcessing/**",
  1264. "AlturaPorts/OMS/Headers",
  1265. "AlturaPorts/Fic/Interfaces/**",
  1266. "AlturaPorts/Fic/Source/SignalNets",
  1267. "AlturaPorts/DSIPublicInterface/PublicHeaders",
  1268. "DAEWin/Include",
  1269. "AlturaPorts/DigiPublic/Interfaces",
  1270. "AlturaPorts/DigiPublic",
  1271. "AlturaPorts/NewFileLibs/DOA",
  1272. "AlturaPorts/NewFileLibs/Cmn",
  1273. "xplat/AVX/avx2/avx2sdk/inc",
  1274. "xplat/AVX/avx2/avx2sdk/utils" };
  1275. for (auto* path : p)
  1276. owner.addProjectPathToBuildPathList (targetExtraSearchPaths, rtasFolder.getChildFile (path));
  1277. }
  1278. return targetExtraSearchPaths;
  1279. }
  1280. bool isUsingClangCppLibrary (const BuildConfiguration& config) const
  1281. {
  1282. if (auto xcodeConfig = dynamic_cast<const XcodeBuildConfiguration*> (&config))
  1283. {
  1284. const auto& configValue = xcodeConfig->cppStandardLibrary.get();
  1285. if (configValue.isNotEmpty())
  1286. return (configValue == "libc++");
  1287. auto minorOSXDeploymentTarget = getOSXDeploymentTarget (*xcodeConfig)
  1288. .fromLastOccurrenceOf (".", false, false)
  1289. .getIntValue();
  1290. return (minorOSXDeploymentTarget > 8);
  1291. }
  1292. return false;
  1293. }
  1294. String getOSXDeploymentTarget (const XcodeBuildConfiguration& config, String* sdkRoot = nullptr) const
  1295. {
  1296. const String sdk (config.osxSDKVersion.get());
  1297. const String sdkCompat (config.osxDeploymentTarget.get());
  1298. // The AUv3 target always needs to be at least 10.11
  1299. int oldestAllowedDeploymentTarget = (type == Target::AudioUnitv3PlugIn ? minimumAUv3SDKVersion
  1300. : oldestSDKVersion);
  1301. // if the user doesn't set it, then use the last known version that works well with JUCE
  1302. String deploymentTarget = "10.11";
  1303. for (int ver = oldestAllowedDeploymentTarget; ver <= currentSDKVersion; ++ver)
  1304. {
  1305. if (sdk == getSDKName (ver) && sdkRoot != nullptr) *sdkRoot = String ("macosx10." + String (ver));
  1306. if (sdkCompat == getSDKName (ver)) deploymentTarget = "10." + String (ver);
  1307. }
  1308. return deploymentTarget;
  1309. }
  1310. String getCodeSignIdentity (const XcodeBuildConfiguration& config) const
  1311. {
  1312. if (config.codeSignIdentity.isUsingDefault())
  1313. return owner.iOS ? "iPhone Developer" : "Mac Developer";
  1314. return config.codeSignIdentity.get();
  1315. }
  1316. bool isUsingCodeSigning (const XcodeBuildConfiguration& config) const
  1317. {
  1318. return (! config.codeSignIdentity.isUsingDefault())
  1319. || owner.getIosDevelopmentTeamIDString().isNotEmpty();
  1320. }
  1321. //==============================================================================
  1322. const XcodeProjectExporter& owner;
  1323. Target& operator= (const Target&) JUCE_DELETED_FUNCTION;
  1324. };
  1325. mutable StringArray xcodeFrameworks;
  1326. StringArray xcodeLibs;
  1327. private:
  1328. //==============================================================================
  1329. friend class CLionProjectExporter;
  1330. bool xcodeCanUseDwarf;
  1331. OwnedArray<XcodeTarget> targets;
  1332. mutable OwnedArray<ValueTree> pbxBuildFiles, pbxFileReferences, pbxGroups, misc, projectConfigs, targetConfigs;
  1333. mutable StringArray resourceIDs, sourceIDs, targetIDs;
  1334. mutable StringArray frameworkFileIDs, rezFileIDs, resourceFileRefs;
  1335. mutable File menuNibFile, iconFile;
  1336. mutable StringArray buildProducts;
  1337. const bool iOS;
  1338. static String sanitisePath (const String& path)
  1339. {
  1340. if (path.startsWithChar ('~'))
  1341. return "$(HOME)" + path.substring (1);
  1342. return path;
  1343. }
  1344. static String addQuotesIfRequired (const String& s)
  1345. {
  1346. return s.containsAnyOf (" $") ? s.quoted() : s;
  1347. }
  1348. File getProjectBundle() const { return getTargetFolder().getChildFile (project.getProjectFilenameRoot()).withFileExtension (".xcodeproj"); }
  1349. //==============================================================================
  1350. void createObjects() const
  1351. {
  1352. prepareTargets();
  1353. addFrameworks();
  1354. addCustomResourceFolders();
  1355. addPlistFileReferences();
  1356. if (iOS && ! projectType.isStaticLibrary())
  1357. addXcassets();
  1358. else
  1359. addNibFiles();
  1360. addIcons();
  1361. addBuildConfigurations();
  1362. addProjectConfigList (projectConfigs, createID ("__projList"));
  1363. {
  1364. StringArray topLevelGroupIDs;
  1365. addFilesAndGroupsToProject (topLevelGroupIDs);
  1366. addBuildPhases();
  1367. addExtraGroupsToProject (topLevelGroupIDs);
  1368. addGroup (createID ("__mainsourcegroup"), "Source", topLevelGroupIDs);
  1369. }
  1370. addProjectObject();
  1371. removeMismatchedXcuserdata();
  1372. }
  1373. void prepareTargets() const
  1374. {
  1375. for (auto* target : targets)
  1376. {
  1377. if (target->type == XcodeTarget::AggregateTarget)
  1378. continue;
  1379. target->addMainBuildProduct();
  1380. String targetName = target->getName();
  1381. String fileID (createID (targetName + String ("__targetbuildref")));
  1382. String fileRefID (createID (String ("__productFileID") + targetName));
  1383. ValueTree* v = new ValueTree (fileID);
  1384. v->setProperty ("isa", "PBXBuildFile", nullptr);
  1385. v->setProperty ("fileRef", fileRefID, nullptr);
  1386. target->mainBuildProductID = fileID;
  1387. pbxBuildFiles.add (v);
  1388. target->addDependency();
  1389. }
  1390. }
  1391. void addPlistFileReferences() const
  1392. {
  1393. for (auto* target : targets)
  1394. {
  1395. if (target->type == XcodeTarget::AggregateTarget)
  1396. continue;
  1397. if (target->shouldCreatePList())
  1398. {
  1399. RelativePath plistPath (target->infoPlistFile, getTargetFolder(), RelativePath::buildTargetFolder);
  1400. addFileReference (plistPath.toUnixStyle());
  1401. resourceFileRefs.add (createFileRefID (plistPath));
  1402. }
  1403. }
  1404. }
  1405. void addNibFiles() const
  1406. {
  1407. MemoryOutputStream nib;
  1408. nib.write (BinaryData::RecentFilesMenuTemplate_nib, BinaryData::RecentFilesMenuTemplate_nibSize);
  1409. overwriteFileIfDifferentOrThrow (menuNibFile, nib);
  1410. RelativePath menuNibPath (menuNibFile, getTargetFolder(), RelativePath::buildTargetFolder);
  1411. addFileReference (menuNibPath.toUnixStyle());
  1412. resourceIDs.add (addBuildFile (menuNibPath, false, false));
  1413. resourceFileRefs.add (createFileRefID (menuNibPath));
  1414. }
  1415. void addIcons() const
  1416. {
  1417. if (iconFile.exists())
  1418. {
  1419. RelativePath iconPath (iconFile, getTargetFolder(), RelativePath::buildTargetFolder);
  1420. addFileReference (iconPath.toUnixStyle());
  1421. resourceIDs.add (addBuildFile (iconPath, false, false));
  1422. resourceFileRefs.add (createFileRefID (iconPath));
  1423. }
  1424. }
  1425. void addBuildConfigurations() const
  1426. {
  1427. for (ConstConfigIterator config (*this); config.next();)
  1428. {
  1429. const auto& xcodeConfig = dynamic_cast<const XcodeBuildConfiguration&> (*config);
  1430. StringArray settingsLines;
  1431. const auto configSettings = getProjectSettings (xcodeConfig);
  1432. for (auto& key : configSettings.getAllKeys())
  1433. settingsLines.add (key + " = " + configSettings[key]);
  1434. addProjectConfig (config->getName(), settingsLines);
  1435. }
  1436. }
  1437. void addFilesAndGroupsToProject (StringArray& topLevelGroupIDs) const
  1438. {
  1439. StringPairArray entitlements = getEntitlements();
  1440. if (entitlements.size() > 0)
  1441. topLevelGroupIDs.add (addEntitlementsFile (entitlements));
  1442. for (auto& group : getAllGroups())
  1443. if (group.getNumChildren() > 0)
  1444. topLevelGroupIDs.add (addProjectItem (group));
  1445. }
  1446. void addExtraGroupsToProject (StringArray& topLevelGroupIDs) const
  1447. {
  1448. { // Add 'resources' group
  1449. String resourcesGroupID (createID ("__resources"));
  1450. addGroup (resourcesGroupID, "Resources", resourceFileRefs);
  1451. topLevelGroupIDs.add (resourcesGroupID);
  1452. }
  1453. { // Add 'frameworks' group
  1454. String frameworksGroupID (createID ("__frameworks"));
  1455. addGroup (frameworksGroupID, "Frameworks", frameworkFileIDs);
  1456. topLevelGroupIDs.add (frameworksGroupID);
  1457. }
  1458. { // Add 'products' group
  1459. String productsGroupID (createID ("__products"));
  1460. addGroup (productsGroupID, "Products", buildProducts);
  1461. topLevelGroupIDs.add (productsGroupID);
  1462. }
  1463. }
  1464. void addBuildPhases() const
  1465. {
  1466. // add build phases
  1467. for (auto* target : targets)
  1468. {
  1469. if (target->type != XcodeTarget::AggregateTarget)
  1470. buildProducts.add (createID (String ("__productFileID") + String (target->getName())));
  1471. for (ConstConfigIterator config (*this); config.next();)
  1472. {
  1473. const XcodeBuildConfiguration& xcodeConfig = dynamic_cast<const XcodeBuildConfiguration&> (*config);
  1474. const auto configSettings = target->getTargetSettings (xcodeConfig);
  1475. StringArray settingsLines;
  1476. for (auto& key : configSettings.getAllKeys())
  1477. settingsLines.add (key + " = " + configSettings.getValue (key, "\"\""));
  1478. target->addTargetConfig (config->getName(), settingsLines);
  1479. }
  1480. addConfigList (*target, targetConfigs, createID (String ("__configList") + target->getName()));
  1481. target->addShellScriptBuildPhase ("Pre-build script", getPreBuildScript());
  1482. if (target->type != XcodeTarget::AggregateTarget)
  1483. {
  1484. auto skipAUv3 = (target->type == XcodeTarget::AudioUnitv3PlugIn
  1485. && ! shouldDuplicateResourcesFolderForAppExtension());
  1486. if (! projectType.isStaticLibrary() && target->type != XcodeTarget::SharedCodeTarget && ! skipAUv3)
  1487. target->addBuildPhase ("PBXResourcesBuildPhase", resourceIDs);
  1488. StringArray rezFiles (rezFileIDs);
  1489. rezFiles.addArray (target->rezFileIDs);
  1490. if (rezFiles.size() > 0)
  1491. target->addBuildPhase ("PBXRezBuildPhase", rezFiles);
  1492. StringArray sourceFiles (target->sourceIDs);
  1493. if (target->type == XcodeTarget::SharedCodeTarget
  1494. || (! project.getProjectType().isAudioPlugin()))
  1495. sourceFiles.addArray (sourceIDs);
  1496. target->addBuildPhase ("PBXSourcesBuildPhase", sourceFiles);
  1497. if (! projectType.isStaticLibrary() && target->type != XcodeTarget::SharedCodeTarget)
  1498. target->addBuildPhase ("PBXFrameworksBuildPhase", target->frameworkIDs);
  1499. }
  1500. target->addShellScriptBuildPhase ("Post-build script", getPostBuildScript());
  1501. if (project.getProjectType().isAudioPlugin() && project.shouldBuildAUv3()
  1502. && project.shouldBuildStandalonePlugin() && target->type == XcodeTarget::StandalonePlugIn)
  1503. embedAppExtension();
  1504. addTargetObject (*target);
  1505. }
  1506. }
  1507. void embedAppExtension() const
  1508. {
  1509. if (auto* standaloneTarget = getTargetOfType (XcodeTarget::StandalonePlugIn))
  1510. {
  1511. if (auto* auv3Target = getTargetOfType (XcodeTarget::AudioUnitv3PlugIn))
  1512. {
  1513. StringArray files;
  1514. files.add (auv3Target->mainBuildProductID);
  1515. standaloneTarget->addCopyFilesPhase ("Embed App Extensions", files, kPluginsFolder);
  1516. }
  1517. }
  1518. }
  1519. static Image fixMacIconImageSize (Drawable& image)
  1520. {
  1521. const int validSizes[] = { 16, 32, 48, 128, 256, 512, 1024 };
  1522. const int w = image.getWidth();
  1523. const int h = image.getHeight();
  1524. int bestSize = 16;
  1525. for (int size : validSizes)
  1526. {
  1527. if (w == h && w == size)
  1528. {
  1529. bestSize = w;
  1530. break;
  1531. }
  1532. if (jmax (w, h) > size)
  1533. bestSize = size;
  1534. }
  1535. return rescaleImageForIcon (image, bestSize);
  1536. }
  1537. //==============================================================================
  1538. XcodeTarget* getTargetOfType (ProjectType::Target::Type type) const
  1539. {
  1540. for (auto& target : targets)
  1541. if (target->type == type)
  1542. return target;
  1543. return nullptr;
  1544. }
  1545. void addTargetObject (XcodeTarget& target) const
  1546. {
  1547. String targetName = target.getName();
  1548. String targetID = target.getID();
  1549. ValueTree* const v = new ValueTree (targetID);
  1550. v->setProperty ("isa", target.type == XcodeTarget::AggregateTarget ? "PBXAggregateTarget" : "PBXNativeTarget", nullptr);
  1551. v->setProperty ("buildConfigurationList", createID (String ("__configList") + targetName), nullptr);
  1552. v->setProperty ("buildPhases", indentParenthesisedList (target.buildPhaseIDs), nullptr);
  1553. v->setProperty ("buildRules", "( )", nullptr);
  1554. v->setProperty ("dependencies", indentParenthesisedList (getTargetDependencies (target)), nullptr);
  1555. v->setProperty (Ids::name, target.getXcodeSchemeName(), nullptr);
  1556. v->setProperty ("productName", projectName, nullptr);
  1557. if (target.type != XcodeTarget::AggregateTarget)
  1558. {
  1559. v->setProperty ("productReference", createID (String ("__productFileID") + targetName), nullptr);
  1560. jassert (target.xcodeProductType.isNotEmpty());
  1561. v->setProperty ("productType", target.xcodeProductType, nullptr);
  1562. }
  1563. targetIDs.add (targetID);
  1564. misc.add (v);
  1565. }
  1566. StringArray getTargetDependencies (const XcodeTarget& target) const
  1567. {
  1568. StringArray dependencies;
  1569. if (project.getProjectType().isAudioPlugin())
  1570. {
  1571. if (target.type == XcodeTarget::StandalonePlugIn) // depends on AUv3 and shared code
  1572. {
  1573. if (XcodeTarget* auv3Target = getTargetOfType (XcodeTarget::AudioUnitv3PlugIn))
  1574. dependencies.add (auv3Target->getDependencyID());
  1575. if (XcodeTarget* sharedCodeTarget = getTargetOfType (XcodeTarget::SharedCodeTarget))
  1576. dependencies.add (sharedCodeTarget->getDependencyID());
  1577. }
  1578. else if (target.type == XcodeTarget::AggregateTarget) // depends on all other targets
  1579. {
  1580. for (int i = 1; i < targets.size(); ++i)
  1581. dependencies.add (targets[i]->getDependencyID());
  1582. }
  1583. else if (target.type != XcodeTarget::SharedCodeTarget) // shared code doesn't depend on anything; all other targets depend only on the shared code
  1584. {
  1585. if (XcodeTarget* sharedCodeTarget = getTargetOfType (XcodeTarget::SharedCodeTarget))
  1586. dependencies.add (sharedCodeTarget->getDependencyID());
  1587. }
  1588. }
  1589. return dependencies;
  1590. }
  1591. static void writeOldIconFormat (MemoryOutputStream& out, const Image& image, const char* type, const char* maskType)
  1592. {
  1593. const int w = image.getWidth();
  1594. const int h = image.getHeight();
  1595. out.write (type, 4);
  1596. out.writeIntBigEndian (8 + 4 * w * h);
  1597. const Image::BitmapData bitmap (image, Image::BitmapData::readOnly);
  1598. for (int y = 0; y < h; ++y)
  1599. {
  1600. for (int x = 0; x < w; ++x)
  1601. {
  1602. const Colour pixel (bitmap.getPixelColour (x, y));
  1603. out.writeByte ((char) pixel.getAlpha());
  1604. out.writeByte ((char) pixel.getRed());
  1605. out.writeByte ((char) pixel.getGreen());
  1606. out.writeByte ((char) pixel.getBlue());
  1607. }
  1608. }
  1609. out.write (maskType, 4);
  1610. out.writeIntBigEndian (8 + w * h);
  1611. for (int y = 0; y < h; ++y)
  1612. {
  1613. for (int x = 0; x < w; ++x)
  1614. {
  1615. const Colour pixel (bitmap.getPixelColour (x, y));
  1616. out.writeByte ((char) pixel.getAlpha());
  1617. }
  1618. }
  1619. }
  1620. static void writeNewIconFormat (MemoryOutputStream& out, const Image& image, const char* type)
  1621. {
  1622. MemoryOutputStream pngData;
  1623. PNGImageFormat pngFormat;
  1624. pngFormat.writeImageToStream (image, pngData);
  1625. out.write (type, 4);
  1626. out.writeIntBigEndian (8 + (int) pngData.getDataSize());
  1627. out << pngData;
  1628. }
  1629. void writeIcnsFile (const OwnedArray<Drawable>& images, OutputStream& out) const
  1630. {
  1631. MemoryOutputStream data;
  1632. int smallest = 0x7fffffff;
  1633. Drawable* smallestImage = nullptr;
  1634. for (int i = 0; i < images.size(); ++i)
  1635. {
  1636. const Image image (fixMacIconImageSize (*images.getUnchecked(i)));
  1637. jassert (image.getWidth() == image.getHeight());
  1638. if (image.getWidth() < smallest)
  1639. {
  1640. smallest = image.getWidth();
  1641. smallestImage = images.getUnchecked(i);
  1642. }
  1643. switch (image.getWidth())
  1644. {
  1645. case 16: writeOldIconFormat (data, image, "is32", "s8mk"); break;
  1646. case 32: writeOldIconFormat (data, image, "il32", "l8mk"); break;
  1647. case 48: writeOldIconFormat (data, image, "ih32", "h8mk"); break;
  1648. case 128: writeOldIconFormat (data, image, "it32", "t8mk"); break;
  1649. case 256: writeNewIconFormat (data, image, "ic08"); break;
  1650. case 512: writeNewIconFormat (data, image, "ic09"); break;
  1651. case 1024: writeNewIconFormat (data, image, "ic10"); break;
  1652. default: break;
  1653. }
  1654. }
  1655. jassert (data.getDataSize() > 0); // no suitable sized images?
  1656. // If you only supply a 1024 image, the file doesn't work on 10.8, so we need
  1657. // to force a smaller one in there too..
  1658. if (smallest > 512 && smallestImage != nullptr)
  1659. writeNewIconFormat (data, rescaleImageForIcon (*smallestImage, 512), "ic09");
  1660. out.write ("icns", 4);
  1661. out.writeIntBigEndian ((int) data.getDataSize() + 8);
  1662. out << data;
  1663. }
  1664. void getIconImages (OwnedArray<Drawable>& images) const
  1665. {
  1666. ScopedPointer<Drawable> bigIcon (getBigIcon());
  1667. if (bigIcon != nullptr)
  1668. images.add (bigIcon.release());
  1669. ScopedPointer<Drawable> smallIcon (getSmallIcon());
  1670. if (smallIcon != nullptr)
  1671. images.add (smallIcon.release());
  1672. }
  1673. void createiOSIconFiles (File appIconSet) const
  1674. {
  1675. OwnedArray<Drawable> images;
  1676. getIconImages (images);
  1677. if (images.size() > 0)
  1678. {
  1679. for (auto& type : getiOSAppIconTypes())
  1680. {
  1681. auto image = rescaleImageForIcon (*images.getFirst(), type.size);
  1682. if (image.hasAlphaChannel())
  1683. {
  1684. Image background (Image::RGB, image.getWidth(), image.getHeight(), false);
  1685. Graphics g (background);
  1686. g.fillAll (Colours::white);
  1687. g.drawImageWithin (image, 0, 0, image.getWidth(), image.getHeight(),
  1688. RectanglePlacement::centred | RectanglePlacement::onlyReduceInSize);
  1689. image = background;
  1690. }
  1691. MemoryOutputStream pngData;
  1692. PNGImageFormat pngFormat;
  1693. pngFormat.writeImageToStream (image, pngData);
  1694. overwriteFileIfDifferentOrThrow (appIconSet.getChildFile (type.filename), pngData);
  1695. }
  1696. }
  1697. }
  1698. void createIconFile() const
  1699. {
  1700. OwnedArray<Drawable> images;
  1701. getIconImages (images);
  1702. if (images.size() > 0)
  1703. {
  1704. MemoryOutputStream mo;
  1705. writeIcnsFile (images, mo);
  1706. iconFile = getTargetFolder().getChildFile ("Icon.icns");
  1707. overwriteFileIfDifferentOrThrow (iconFile, mo);
  1708. }
  1709. }
  1710. void writeInfoPlistFiles() const
  1711. {
  1712. for (auto& target : targets)
  1713. target->writeInfoPlistFile();
  1714. }
  1715. // Delete .rsrc files in folder but don't follow sym-links
  1716. void deleteRsrcFiles (const File& folder) const
  1717. {
  1718. for (DirectoryIterator di (folder, false, "*", File::findFilesAndDirectories); di.next();)
  1719. {
  1720. const File& entry = di.getFile();
  1721. if (! entry.isSymbolicLink())
  1722. {
  1723. if (entry.existsAsFile() && entry.getFileExtension().toLowerCase() == ".rsrc")
  1724. entry.deleteFile();
  1725. else if (entry.isDirectory())
  1726. deleteRsrcFiles (entry);
  1727. }
  1728. }
  1729. }
  1730. static String getLinkerFlagForLib (String library)
  1731. {
  1732. if (library.substring (0, 3) == "lib")
  1733. library = library.substring (3);
  1734. return "-l" + library.replace (" ", "\\\\ ").upToLastOccurrenceOf (".", false, false);
  1735. }
  1736. String getSearchPathForStaticLibrary (const RelativePath& library) const
  1737. {
  1738. String searchPath (library.toUnixStyle().upToLastOccurrenceOf ("/", false, false));
  1739. if (! library.isAbsolute())
  1740. {
  1741. String srcRoot (rebaseFromProjectFolderToBuildTarget (RelativePath (".", RelativePath::projectFolder)).toUnixStyle());
  1742. if (srcRoot.endsWith ("/.")) srcRoot = srcRoot.dropLastCharacters (2);
  1743. if (! srcRoot.endsWithChar ('/')) srcRoot << '/';
  1744. searchPath = srcRoot + searchPath;
  1745. }
  1746. return sanitisePath (searchPath);
  1747. }
  1748. StringPairArray getProjectSettings (const XcodeBuildConfiguration& config) const
  1749. {
  1750. StringPairArray s;
  1751. s.set ("ALWAYS_SEARCH_USER_PATHS", "NO");
  1752. s.set ("ENABLE_STRICT_OBJC_MSGSEND", "YES");
  1753. s.set ("GCC_C_LANGUAGE_STANDARD", "c11");
  1754. s.set ("GCC_NO_COMMON_BLOCKS", "YES");
  1755. s.set ("GCC_MODEL_TUNING", "G5");
  1756. s.set ("GCC_WARN_ABOUT_RETURN_TYPE", "YES");
  1757. s.set ("GCC_WARN_CHECK_SWITCH_STATEMENTS", "YES");
  1758. s.set ("GCC_WARN_UNUSED_VARIABLE", "YES");
  1759. s.set ("GCC_WARN_MISSING_PARENTHESES", "YES");
  1760. s.set ("GCC_WARN_NON_VIRTUAL_DESTRUCTOR", "YES");
  1761. s.set ("GCC_WARN_TYPECHECK_CALLS_TO_PRINTF", "YES");
  1762. s.set ("GCC_WARN_64_TO_32_BIT_CONVERSION", "YES");
  1763. s.set ("GCC_WARN_UNDECLARED_SELECTOR", "YES");
  1764. s.set ("GCC_WARN_UNINITIALIZED_AUTOS", "YES");
  1765. s.set ("GCC_WARN_UNUSED_FUNCTION", "YES");
  1766. s.set ("CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING", "YES");
  1767. s.set ("CLANG_WARN_BOOL_CONVERSION", "YES");
  1768. s.set ("CLANG_WARN_COMMA", "YES");
  1769. s.set ("CLANG_WARN_CONSTANT_CONVERSION", "YES");
  1770. s.set ("CLANG_WARN_EMPTY_BODY", "YES");
  1771. s.set ("CLANG_WARN_ENUM_CONVERSION", "YES");
  1772. s.set ("CLANG_WARN_INFINITE_RECURSION", "YES");
  1773. s.set ("CLANG_WARN_INT_CONVERSION", "YES");
  1774. s.set ("CLANG_WARN_NON_LITERAL_NULL_CONVERSION", "YES");
  1775. s.set ("CLANG_WARN_OBJC_LITERAL_CONVERSION", "YES");
  1776. s.set ("CLANG_WARN_RANGE_LOOP_ANALYSIS", "YES");
  1777. s.set ("CLANG_WARN_STRICT_PROTOTYPES", "YES");
  1778. s.set ("CLANG_WARN_SUSPICIOUS_MOVE", "YES");
  1779. s.set ("CLANG_WARN_UNREACHABLE_CODE", "YES");
  1780. s.set ("CLANG_WARN__DUPLICATE_METHOD_MATCH", "YES");
  1781. s.set ("WARNING_CFLAGS", "-Wreorder");
  1782. if (projectType.isStaticLibrary())
  1783. {
  1784. s.set ("GCC_INLINES_ARE_PRIVATE_EXTERN", "NO");
  1785. s.set ("GCC_SYMBOLS_PRIVATE_EXTERN", "NO");
  1786. }
  1787. else
  1788. {
  1789. s.set ("GCC_INLINES_ARE_PRIVATE_EXTERN", "YES");
  1790. }
  1791. if (config.isDebug())
  1792. {
  1793. s.set ("ENABLE_TESTABILITY", "YES");
  1794. if (config.osxArchitecture.get() == osxArch_Default || config.osxArchitecture.get().isEmpty())
  1795. s.set ("ONLY_ACTIVE_ARCH", "YES");
  1796. }
  1797. if (iOS)
  1798. {
  1799. s.set ("\"CODE_SIGN_IDENTITY[sdk=iphoneos*]\"", config.codeSignIdentity.get().quoted());
  1800. s.set ("SDKROOT", "iphoneos");
  1801. s.set ("TARGETED_DEVICE_FAMILY", getDeviceFamilyString().quoted());
  1802. const String iosVersion (config.iosDeploymentTarget.get());
  1803. if (iosVersion.isNotEmpty() && iosVersion != osxVersionDefault)
  1804. s.set ("IPHONEOS_DEPLOYMENT_TARGET", iosVersion);
  1805. else
  1806. s.set ("IPHONEOS_DEPLOYMENT_TARGET", "9.3");
  1807. }
  1808. else
  1809. {
  1810. if (! config.codeSignIdentity.isUsingDefault() || getIosDevelopmentTeamIDString().isNotEmpty())
  1811. s.set ("CODE_SIGN_IDENTITY", config.codeSignIdentity.get().quoted());
  1812. }
  1813. s.set ("ZERO_LINK", "NO");
  1814. if (xcodeCanUseDwarf)
  1815. s.set ("DEBUG_INFORMATION_FORMAT", "\"dwarf\"");
  1816. s.set ("PRODUCT_NAME", replacePreprocessorTokens (config, config.getTargetBinaryNameString()).quoted());
  1817. return s;
  1818. }
  1819. void addFrameworks() const
  1820. {
  1821. if (! projectType.isStaticLibrary())
  1822. {
  1823. if (isInAppPurchasesEnabled())
  1824. xcodeFrameworks.addIfNotAlreadyThere ("StoreKit");
  1825. if (iOS && isPushNotificationsEnabled())
  1826. xcodeFrameworks.addIfNotAlreadyThere ("UserNotifications");
  1827. xcodeFrameworks.addTokens (getExtraFrameworksString(), ",;", "\"'");
  1828. xcodeFrameworks.trim();
  1829. StringArray s (xcodeFrameworks);
  1830. for (auto& target : targets)
  1831. s.addArray (target->xcodeFrameworks);
  1832. if (project.getConfigFlag ("JUCE_QUICKTIME") == Project::configFlagDisabled)
  1833. s.removeString ("QuickTime");
  1834. s.trim();
  1835. s.removeDuplicates (true);
  1836. s.sort (true);
  1837. for (auto& framework : s)
  1838. {
  1839. String frameworkID = addFramework (framework);
  1840. // find all the targets that are referring to this object
  1841. for (auto& target : targets)
  1842. {
  1843. if (xcodeFrameworks.contains (framework) || target->xcodeFrameworks.contains (framework))
  1844. {
  1845. target->frameworkIDs.add (frameworkID);
  1846. target->frameworkNames.add (framework);
  1847. }
  1848. }
  1849. }
  1850. }
  1851. }
  1852. void addCustomResourceFolders() const
  1853. {
  1854. StringArray folders;
  1855. folders.addTokens (getCustomResourceFoldersString(), ":", "");
  1856. folders.trim();
  1857. for (auto& crf : folders)
  1858. addCustomResourceFolder (crf);
  1859. }
  1860. void addXcassets() const
  1861. {
  1862. String customXcassetsPath = getCustomXcassetsFolderString();
  1863. if (customXcassetsPath.isEmpty())
  1864. createXcassetsFolderFromIcons();
  1865. else
  1866. addCustomResourceFolder (customXcassetsPath, "folder.assetcatalog");
  1867. }
  1868. void addCustomResourceFolder (String folderPathRelativeToProjectFolder, const String fileType = "folder") const
  1869. {
  1870. String folderPath = RelativePath (folderPathRelativeToProjectFolder, RelativePath::projectFolder)
  1871. .rebased (projectFolder, getTargetFolder(), RelativePath::buildTargetFolder)
  1872. .toUnixStyle();
  1873. const String fileRefID (createFileRefID (folderPath));
  1874. addFileOrFolderReference (folderPath, "<group>", fileType);
  1875. resourceIDs.add (addBuildFile (folderPath, fileRefID, false, false));
  1876. resourceFileRefs.add (createFileRefID (folderPath));
  1877. }
  1878. //==============================================================================
  1879. void writeProjectFile (OutputStream& output) const
  1880. {
  1881. output << "// !$*UTF8*$!\n{\n"
  1882. "\tarchiveVersion = 1;\n"
  1883. "\tclasses = {\n\t};\n"
  1884. "\tobjectVersion = 46;\n"
  1885. "\tobjects = {\n\n";
  1886. Array<ValueTree*> objects;
  1887. objects.addArray (pbxBuildFiles);
  1888. objects.addArray (pbxFileReferences);
  1889. objects.addArray (pbxGroups);
  1890. objects.addArray (targetConfigs);
  1891. objects.addArray (projectConfigs);
  1892. objects.addArray (misc);
  1893. for (auto* o : objects)
  1894. {
  1895. output << "\t\t" << o->getType().toString() << " = {";
  1896. for (int j = 0; j < o->getNumProperties(); ++j)
  1897. {
  1898. const Identifier propertyName (o->getPropertyName(j));
  1899. String val (o->getProperty (propertyName).toString());
  1900. if (val.isEmpty() || (val.containsAnyOf (" \t;<>()=,&+-_@~\r\n\\#%^`*")
  1901. && ! (val.trimStart().startsWithChar ('(')
  1902. || val.trimStart().startsWithChar ('{'))))
  1903. val = "\"" + val + "\"";
  1904. output << propertyName.toString() << " = " << val << "; ";
  1905. }
  1906. output << "};\n";
  1907. }
  1908. output << "\t};\n\trootObject = " << createID ("__root") << ";\n}\n";
  1909. }
  1910. String addBuildFile (const String& path, const String& fileRefID, bool addToSourceBuildPhase, bool inhibitWarnings, XcodeTarget* xcodeTarget = nullptr) const
  1911. {
  1912. String fileID (createID (path + "buildref"));
  1913. if (addToSourceBuildPhase)
  1914. {
  1915. if (xcodeTarget != nullptr)
  1916. xcodeTarget->sourceIDs.add (fileID);
  1917. else
  1918. sourceIDs.add (fileID);
  1919. }
  1920. ValueTree* v = new ValueTree (fileID);
  1921. v->setProperty ("isa", "PBXBuildFile", nullptr);
  1922. v->setProperty ("fileRef", fileRefID, nullptr);
  1923. if (inhibitWarnings)
  1924. v->setProperty ("settings", "{COMPILER_FLAGS = \"-w\"; }", nullptr);
  1925. pbxBuildFiles.add (v);
  1926. return fileID;
  1927. }
  1928. String addBuildFile (const RelativePath& path, bool addToSourceBuildPhase, bool inhibitWarnings, XcodeTarget* xcodeTarget = nullptr) const
  1929. {
  1930. return addBuildFile (path.toUnixStyle(), createFileRefID (path), addToSourceBuildPhase, inhibitWarnings, xcodeTarget);
  1931. }
  1932. String addFileReference (String pathString) const
  1933. {
  1934. String sourceTree ("SOURCE_ROOT");
  1935. RelativePath path (pathString, RelativePath::unknown);
  1936. if (pathString.startsWith ("${"))
  1937. {
  1938. sourceTree = pathString.substring (2).upToFirstOccurrenceOf ("}", false, false);
  1939. pathString = pathString.fromFirstOccurrenceOf ("}/", false, false);
  1940. }
  1941. else if (path.isAbsolute())
  1942. {
  1943. sourceTree = "<absolute>";
  1944. }
  1945. String fileType = getFileType (path);
  1946. return addFileOrFolderReference (pathString, sourceTree, fileType);
  1947. }
  1948. String addFileOrFolderReference (String pathString, String sourceTree, String fileType) const
  1949. {
  1950. const String fileRefID (createFileRefID (pathString));
  1951. ScopedPointer<ValueTree> v (new ValueTree (fileRefID));
  1952. v->setProperty ("isa", "PBXFileReference", nullptr);
  1953. v->setProperty ("lastKnownFileType", fileType, nullptr);
  1954. v->setProperty (Ids::name, pathString.fromLastOccurrenceOf ("/", false, false), nullptr);
  1955. v->setProperty ("path", pathString, nullptr);
  1956. v->setProperty ("sourceTree", sourceTree, nullptr);
  1957. const int existing = pbxFileReferences.indexOfSorted (*this, v);
  1958. if (existing >= 0)
  1959. {
  1960. // If this fails, there's either a string hash collision, or the same file is being added twice (incorrectly)
  1961. jassert (pbxFileReferences.getUnchecked (existing)->isEquivalentTo (*v));
  1962. }
  1963. else
  1964. {
  1965. pbxFileReferences.addSorted (*this, v.release());
  1966. }
  1967. return fileRefID;
  1968. }
  1969. public:
  1970. static int compareElements (const ValueTree* first, const ValueTree* second)
  1971. {
  1972. return first->getType().getCharPointer().compare (second->getType().getCharPointer());
  1973. }
  1974. private:
  1975. static String getFileType (const RelativePath& file)
  1976. {
  1977. if (file.hasFileExtension (cppFileExtensions)) return "sourcecode.cpp.cpp";
  1978. if (file.hasFileExtension (".mm")) return "sourcecode.cpp.objcpp";
  1979. if (file.hasFileExtension (".m")) return "sourcecode.c.objc";
  1980. if (file.hasFileExtension (".c")) return "sourcecode.c.c";
  1981. if (file.hasFileExtension (headerFileExtensions)) return "sourcecode.c.h";
  1982. if (file.hasFileExtension (asmFileExtensions)) return "sourcecode.c.asm";
  1983. if (file.hasFileExtension (".framework")) return "wrapper.framework";
  1984. if (file.hasFileExtension (".jpeg;.jpg")) return "image.jpeg";
  1985. if (file.hasFileExtension ("png;gif")) return "image" + file.getFileExtension();
  1986. if (file.hasFileExtension ("html;htm")) return "text.html";
  1987. if (file.hasFileExtension ("xml;zip;wav")) return "file" + file.getFileExtension();
  1988. if (file.hasFileExtension ("txt;rtf")) return "text" + file.getFileExtension();
  1989. if (file.hasFileExtension ("plist")) return "text.plist.xml";
  1990. if (file.hasFileExtension ("entitlements")) return "text.plist.xml";
  1991. if (file.hasFileExtension ("app")) return "wrapper.application";
  1992. if (file.hasFileExtension ("component;vst;plugin")) return "wrapper.cfbundle";
  1993. if (file.hasFileExtension ("xcodeproj")) return "wrapper.pb-project";
  1994. if (file.hasFileExtension ("a")) return "archive.ar";
  1995. if (file.hasFileExtension ("xcassets")) return "folder.assetcatalog";
  1996. return "file" + file.getFileExtension();
  1997. }
  1998. String addFile (const RelativePath& path, bool shouldBeCompiled, bool shouldBeAddedToBinaryResources,
  1999. bool shouldBeAddedToXcodeResources, bool inhibitWarnings, XcodeTarget* xcodeTarget) const
  2000. {
  2001. const String pathAsString (path.toUnixStyle());
  2002. const String refID (addFileReference (path.toUnixStyle()));
  2003. if (shouldBeCompiled)
  2004. {
  2005. addBuildFile (pathAsString, refID, true, inhibitWarnings, xcodeTarget);
  2006. }
  2007. else if (! shouldBeAddedToBinaryResources || shouldBeAddedToXcodeResources)
  2008. {
  2009. const String fileType (getFileType (path));
  2010. if (shouldBeAddedToXcodeResources)
  2011. {
  2012. resourceIDs.add (addBuildFile (pathAsString, refID, false, false));
  2013. resourceFileRefs.add (refID);
  2014. }
  2015. }
  2016. return refID;
  2017. }
  2018. String addRezFile (const Project::Item& projectItem, const RelativePath& path) const
  2019. {
  2020. const String pathAsString (path.toUnixStyle());
  2021. const String refID (addFileReference (path.toUnixStyle()));
  2022. if (projectItem.isModuleCode())
  2023. {
  2024. if (XcodeTarget* xcodeTarget = getTargetOfType (getProject().getTargetTypeFromFilePath (projectItem.getFile(), false)))
  2025. {
  2026. String rezFileID = addBuildFile (pathAsString, refID, false, false, xcodeTarget);
  2027. xcodeTarget->rezFileIDs.add (rezFileID);
  2028. return refID;
  2029. }
  2030. }
  2031. return {};
  2032. }
  2033. String getEntitlementsFileName() const
  2034. {
  2035. return project.getProjectFilenameRoot() + String (".entitlements");
  2036. }
  2037. StringPairArray getEntitlements() const
  2038. {
  2039. StringPairArray entitlements;
  2040. if (project.getProjectType().isAudioPlugin())
  2041. {
  2042. if (isiOS())
  2043. {
  2044. if (project.shouldEnableIAA())
  2045. entitlements.set ("inter-app-audio", "<true/>");
  2046. }
  2047. else
  2048. {
  2049. entitlements.set ("com.apple.security.app-sandbox", "<true/>");
  2050. }
  2051. }
  2052. else
  2053. {
  2054. if (isPushNotificationsEnabled())
  2055. entitlements.set (isiOS() ? "aps-environment"
  2056. : "com.apple.developer.aps-environment",
  2057. "<string>development</string>");
  2058. }
  2059. if (isAppGroupsEnabled())
  2060. {
  2061. auto appGroups = StringArray::fromTokens (getAppGroupIdString(), ";", { });
  2062. auto groups = String ("<array>");
  2063. for (auto group : appGroups)
  2064. groups += "\n\t\t<string>" + group.trim() + "</string>";
  2065. groups += "\n\t</array>";
  2066. entitlements.set ("com.apple.security.application-groups", groups);
  2067. }
  2068. if (isiOS() && isiCloudPermissionsEnabled())
  2069. {
  2070. entitlements.set ("com.apple.developer.icloud-container-identifiers",
  2071. "<array>\n"
  2072. " <string>iCloud.$(CFBundleIdentifier)</string>\n"
  2073. " </array>");
  2074. entitlements.set ("com.apple.developer.icloud-services",
  2075. "<array>\n"
  2076. " <string>CloudDocuments</string>\n"
  2077. " </array>");
  2078. entitlements.set ("com.apple.developer.ubiquity-container-identifiers",
  2079. "<array>\n"
  2080. " <string>iCloud.$(CFBundleIdentifier)</string>\n"
  2081. " </array>");
  2082. }
  2083. return entitlements;
  2084. }
  2085. String addEntitlementsFile (StringPairArray entitlements) const
  2086. {
  2087. String content =
  2088. "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
  2089. "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">\n"
  2090. "<plist version=\"1.0\">\n"
  2091. "<dict>\n";
  2092. const auto keys = entitlements.getAllKeys();
  2093. for (auto& key : keys)
  2094. {
  2095. content += "\t<key>" + key + "</key>\n"
  2096. "\t" + entitlements[key] + "\n";
  2097. }
  2098. content += "</dict>\n"
  2099. "</plist>\n";
  2100. File entitlementsFile = getTargetFolder().getChildFile (getEntitlementsFileName());
  2101. overwriteFileIfDifferentOrThrow (entitlementsFile, content);
  2102. RelativePath plistPath (entitlementsFile, getTargetFolder(), RelativePath::buildTargetFolder);
  2103. return addFile (plistPath, false, false, false, false, nullptr);
  2104. }
  2105. String addProjectItem (const Project::Item& projectItem) const
  2106. {
  2107. if (modulesGroup != nullptr && projectItem.getParent() == *modulesGroup)
  2108. return addFileReference (rebaseFromProjectFolderToBuildTarget (getModuleFolderRelativeToProject (projectItem.getName())).toUnixStyle());
  2109. if (projectItem.isGroup())
  2110. {
  2111. StringArray childIDs;
  2112. for (int i = 0; i < projectItem.getNumChildren(); ++i)
  2113. {
  2114. const String childID (addProjectItem (projectItem.getChild(i)));
  2115. if (childID.isNotEmpty())
  2116. childIDs.add (childID);
  2117. }
  2118. return addGroup (projectItem, childIDs);
  2119. }
  2120. if (projectItem.shouldBeAddedToTargetProject())
  2121. {
  2122. const String itemPath (projectItem.getFilePath());
  2123. RelativePath path;
  2124. if (itemPath.startsWith ("${"))
  2125. path = RelativePath (itemPath, RelativePath::unknown);
  2126. else
  2127. path = RelativePath (projectItem.getFile(), getTargetFolder(), RelativePath::buildTargetFolder);
  2128. if (path.hasFileExtension (".r"))
  2129. return addRezFile (projectItem, path);
  2130. XcodeTarget* xcodeTarget = nullptr;
  2131. if (projectItem.isModuleCode() && projectItem.shouldBeCompiled())
  2132. xcodeTarget = getTargetOfType (project.getTargetTypeFromFilePath (projectItem.getFile(), false));
  2133. return addFile (path, projectItem.shouldBeCompiled(),
  2134. projectItem.shouldBeAddedToBinaryResources(),
  2135. projectItem.shouldBeAddedToXcodeResources(),
  2136. projectItem.shouldInhibitWarnings(),
  2137. xcodeTarget);
  2138. }
  2139. return {};
  2140. }
  2141. String addFramework (const String& frameworkName) const
  2142. {
  2143. String path (frameworkName);
  2144. if (! File::isAbsolutePath (path))
  2145. path = "System/Library/Frameworks/" + path;
  2146. if (! path.endsWithIgnoreCase (".framework"))
  2147. path << ".framework";
  2148. const String fileRefID (createFileRefID (path));
  2149. addFileReference ((File::isAbsolutePath (frameworkName) ? "" : "${SDKROOT}/") + path);
  2150. frameworkFileIDs.add (fileRefID);
  2151. return addBuildFile (path, fileRefID, false, false);
  2152. }
  2153. void addGroup (const String& groupID, const String& groupName, const StringArray& childIDs) const
  2154. {
  2155. ValueTree* v = new ValueTree (groupID);
  2156. v->setProperty ("isa", "PBXGroup", nullptr);
  2157. v->setProperty ("children", indentParenthesisedList (childIDs), nullptr);
  2158. v->setProperty (Ids::name, groupName, nullptr);
  2159. v->setProperty ("sourceTree", "<group>", nullptr);
  2160. pbxGroups.add (v);
  2161. }
  2162. String addGroup (const Project::Item& item, StringArray& childIDs) const
  2163. {
  2164. const String groupName (item.getName());
  2165. const String groupID (getIDForGroup (item));
  2166. addGroup (groupID, groupName, childIDs);
  2167. return groupID;
  2168. }
  2169. void addProjectConfig (const String& configName, const StringArray& buildSettings) const
  2170. {
  2171. ValueTree* v = new ValueTree (createID ("projectconfigid_" + configName));
  2172. v->setProperty ("isa", "XCBuildConfiguration", nullptr);
  2173. v->setProperty ("buildSettings", indentBracedList (buildSettings), nullptr);
  2174. v->setProperty (Ids::name, configName, nullptr);
  2175. projectConfigs.add (v);
  2176. }
  2177. void addConfigList (XcodeTarget& target, const OwnedArray <ValueTree>& configsToUse, const String& listID) const
  2178. {
  2179. ValueTree* v = new ValueTree (listID);
  2180. v->setProperty ("isa", "XCConfigurationList", nullptr);
  2181. v->setProperty ("buildConfigurations", indentParenthesisedList (target.configIDs), nullptr);
  2182. v->setProperty ("defaultConfigurationIsVisible", (int) 0, nullptr);
  2183. if (auto* first = configsToUse.getFirst())
  2184. v->setProperty ("defaultConfigurationName", first->getProperty (Ids::name), nullptr);
  2185. misc.add (v);
  2186. }
  2187. void addProjectConfigList (const OwnedArray <ValueTree>& configsToUse, const String& listID) const
  2188. {
  2189. StringArray configIDs;
  2190. for (auto* c : configsToUse)
  2191. configIDs.add (c->getType().toString());
  2192. ValueTree* v = new ValueTree (listID);
  2193. v->setProperty ("isa", "XCConfigurationList", nullptr);
  2194. v->setProperty ("buildConfigurations", indentParenthesisedList (configIDs), nullptr);
  2195. v->setProperty ("defaultConfigurationIsVisible", (int) 0, nullptr);
  2196. if (auto* first = configsToUse.getFirst())
  2197. v->setProperty ("defaultConfigurationName", first->getProperty (Ids::name), nullptr);
  2198. misc.add (v);
  2199. }
  2200. void addProjectObject() const
  2201. {
  2202. ValueTree* const v = new ValueTree (createID ("__root"));
  2203. v->setProperty ("isa", "PBXProject", nullptr);
  2204. v->setProperty ("buildConfigurationList", createID ("__projList"), nullptr);
  2205. v->setProperty ("attributes", getProjectObjectAttributes(), nullptr);
  2206. v->setProperty ("compatibilityVersion", "Xcode 3.2", nullptr);
  2207. v->setProperty ("hasScannedForEncodings", (int) 0, nullptr);
  2208. v->setProperty ("mainGroup", createID ("__mainsourcegroup"), nullptr);
  2209. v->setProperty ("projectDirPath", "\"\"", nullptr);
  2210. v->setProperty ("projectRoot", "\"\"", nullptr);
  2211. String targetString = "(" + targetIDs.joinIntoString (", ") + ")";
  2212. v->setProperty ("targets", targetString, nullptr);
  2213. misc.add (v);
  2214. }
  2215. //==============================================================================
  2216. void removeMismatchedXcuserdata() const
  2217. {
  2218. if (settings ["keepCustomXcodeSchemes"])
  2219. return;
  2220. File xcuserdata = getProjectBundle().getChildFile ("xcuserdata");
  2221. if (! xcuserdata.exists())
  2222. return;
  2223. if (! xcuserdataMatchesTargets (xcuserdata))
  2224. {
  2225. xcuserdata.deleteRecursively();
  2226. getProjectBundle().getChildFile ("project.xcworkspace").deleteRecursively();
  2227. }
  2228. }
  2229. bool xcuserdataMatchesTargets (const File& xcuserdata) const
  2230. {
  2231. Array<File> xcschemeManagementPlists;
  2232. xcuserdata.findChildFiles (xcschemeManagementPlists, File::findFiles, true, "xcschememanagement.plist");
  2233. for (auto& plist : xcschemeManagementPlists)
  2234. if (! xcschemeManagementPlistMatchesTargets (plist))
  2235. return false;
  2236. return true;
  2237. }
  2238. static StringArray parseNamesOfTargetsFromPlist (const XmlElement& dictXML)
  2239. {
  2240. forEachXmlChildElementWithTagName (dictXML, schemesKey, "key")
  2241. {
  2242. if (schemesKey->getAllSubText().trim().equalsIgnoreCase ("SchemeUserState"))
  2243. {
  2244. if (auto* dict = schemesKey->getNextElement())
  2245. {
  2246. if (dict->hasTagName ("dict"))
  2247. {
  2248. StringArray names;
  2249. forEachXmlChildElementWithTagName (*dict, key, "key")
  2250. names.add (key->getAllSubText().upToLastOccurrenceOf (".xcscheme", false, false).trim());
  2251. names.sort (false);
  2252. return names;
  2253. }
  2254. }
  2255. }
  2256. }
  2257. return {};
  2258. }
  2259. StringArray getNamesOfTargets() const
  2260. {
  2261. StringArray names;
  2262. for (auto& target : targets)
  2263. names.add (target->getXcodeSchemeName());
  2264. names.sort (false);
  2265. return names;
  2266. }
  2267. bool xcschemeManagementPlistMatchesTargets (const File& plist) const
  2268. {
  2269. ScopedPointer<XmlElement> xml (XmlDocument::parse (plist));
  2270. if (xml != nullptr)
  2271. if (auto* dict = xml->getChildByName ("dict"))
  2272. return parseNamesOfTargetsFromPlist (*dict) == getNamesOfTargets();
  2273. return false;
  2274. }
  2275. //==============================================================================
  2276. struct AppIconType
  2277. {
  2278. const char* idiom;
  2279. const char* sizeString;
  2280. const char* filename;
  2281. const char* scale;
  2282. int size;
  2283. };
  2284. static Array<AppIconType> getiOSAppIconTypes()
  2285. {
  2286. AppIconType types[] =
  2287. {
  2288. { "iphone", "20x20", "Icon-Notification-20@2x.png", "2x", 40 },
  2289. { "iphone", "20x20", "Icon-Notification-20@3x.png", "3x", 60 },
  2290. { "iphone", "29x29", "Icon-29.png", "1x", 29 },
  2291. { "iphone", "29x29", "Icon-29@2x.png", "2x", 58 },
  2292. { "iphone", "29x29", "Icon-29@3x.png", "3x", 87 },
  2293. { "iphone", "40x40", "Icon-Spotlight-40@2x.png", "2x", 80 },
  2294. { "iphone", "40x40", "Icon-Spotlight-40@3x.png", "3x", 120 },
  2295. { "iphone", "57x57", "Icon.png", "1x", 57 },
  2296. { "iphone", "57x57", "Icon@2x.png", "2x", 114 },
  2297. { "iphone", "60x60", "Icon-60@2x.png", "2x", 120 },
  2298. { "iphone", "60x60", "Icon-@3x.png", "3x", 180 },
  2299. { "ipad", "20x20", "Icon-Notifications-20.png", "1x", 20 },
  2300. { "ipad", "20x20", "Icon-Notifications-20@2x.png", "2x", 40 },
  2301. { "ipad", "29x29", "Icon-Small-1.png", "1x", 29 },
  2302. { "ipad", "29x29", "Icon-Small@2x-1.png", "2x", 58 },
  2303. { "ipad", "40x40", "Icon-Spotlight-40.png", "1x", 40 },
  2304. { "ipad", "40x40", "Icon-Spotlight-40@2x-1.png", "2x", 80 },
  2305. { "ipad", "50x50", "Icon-Small-50.png", "1x", 50 },
  2306. { "ipad", "50x50", "Icon-Small-50@2x.png", "2x", 100 },
  2307. { "ipad", "72x72", "Icon-72.png", "1x", 72 },
  2308. { "ipad", "72x72", "Icon-72@2x.png", "2x", 144 },
  2309. { "ipad", "76x76", "Icon-76.png", "1x", 76 },
  2310. { "ipad", "76x76", "Icon-76@2x.png", "2x", 152 },
  2311. { "ipad", "83.5x83.5", "Icon-83.5@2x.png", "2x", 167 },
  2312. { "ios-marketing", "1024x1024", "Icon-AppStore-1024.png", "1x", 1024 }
  2313. };
  2314. return Array<AppIconType> (types, numElementsInArray (types));
  2315. }
  2316. static String getiOSAppIconContents()
  2317. {
  2318. var images;
  2319. for (auto& type : getiOSAppIconTypes())
  2320. {
  2321. DynamicObject::Ptr d = new DynamicObject();
  2322. d->setProperty ("idiom", type.idiom);
  2323. d->setProperty ("size", type.sizeString);
  2324. d->setProperty ("filename", type.filename);
  2325. d->setProperty ("scale", type.scale);
  2326. images.append (var (d.get()));
  2327. }
  2328. return getiOSAssetContents (images);
  2329. }
  2330. String getProjectObjectAttributes() const
  2331. {
  2332. String attributes;
  2333. attributes << "{ LastUpgradeCheck = 0830; "
  2334. << "ORGANIZATIONNAME = " << getProject().getCompanyNameString().quoted()
  2335. <<"; ";
  2336. if (projectType.isGUIApplication() || projectType.isAudioPlugin())
  2337. {
  2338. attributes << "TargetAttributes = { ";
  2339. for (auto& target : targets)
  2340. attributes << target->getTargetAttributes();
  2341. attributes << " }; ";
  2342. }
  2343. attributes << "}";
  2344. return attributes;
  2345. }
  2346. //==============================================================================
  2347. struct ImageType
  2348. {
  2349. const char* orientation;
  2350. const char* idiom;
  2351. const char* subtype;
  2352. const char* extent;
  2353. const char* scale;
  2354. const char* filename;
  2355. int width;
  2356. int height;
  2357. };
  2358. static Array<ImageType> getiOSLaunchImageTypes()
  2359. {
  2360. ImageType types[] =
  2361. {
  2362. { "portrait", "iphone", nullptr, "full-screen", "2x", "LaunchImage-iphone-2x.png", 640, 960 },
  2363. { "portrait", "iphone", "retina4", "full-screen", "2x", "LaunchImage-iphone-retina4.png", 640, 1136 },
  2364. { "portrait", "ipad", nullptr, "full-screen", "1x", "LaunchImage-ipad-portrait-1x.png", 768, 1024 },
  2365. { "landscape","ipad", nullptr, "full-screen", "1x", "LaunchImage-ipad-landscape-1x.png", 1024, 768 },
  2366. { "portrait", "ipad", nullptr, "full-screen", "2x", "LaunchImage-ipad-portrait-2x.png", 1536, 2048 },
  2367. { "landscape","ipad", nullptr, "full-screen", "2x", "LaunchImage-ipad-landscape-2x.png", 2048, 1536 }
  2368. };
  2369. return Array<ImageType> (types, numElementsInArray (types));
  2370. }
  2371. static String getiOSLaunchImageContents()
  2372. {
  2373. var images;
  2374. for (auto& type : getiOSLaunchImageTypes())
  2375. {
  2376. DynamicObject::Ptr d = new DynamicObject();
  2377. d->setProperty ("orientation", type.orientation);
  2378. d->setProperty ("idiom", type.idiom);
  2379. d->setProperty ("extent", type.extent);
  2380. d->setProperty ("minimum-system-version", "7.0");
  2381. d->setProperty ("scale", type.scale);
  2382. d->setProperty ("filename", type.filename);
  2383. if (type.subtype != nullptr)
  2384. d->setProperty ("subtype", type.subtype);
  2385. images.append (var (d.get()));
  2386. }
  2387. return getiOSAssetContents (images);
  2388. }
  2389. static void createiOSLaunchImageFiles (const File& launchImageSet)
  2390. {
  2391. for (auto& type : getiOSLaunchImageTypes())
  2392. {
  2393. Image image (Image::ARGB, type.width, type.height, true); // (empty black image)
  2394. image.clear (image.getBounds(), Colours::black);
  2395. MemoryOutputStream pngData;
  2396. PNGImageFormat pngFormat;
  2397. pngFormat.writeImageToStream (image, pngData);
  2398. overwriteFileIfDifferentOrThrow (launchImageSet.getChildFile (type.filename), pngData);
  2399. }
  2400. }
  2401. //==============================================================================
  2402. static String getiOSAssetContents (var images)
  2403. {
  2404. DynamicObject::Ptr v (new DynamicObject());
  2405. var info (new DynamicObject());
  2406. info.getDynamicObject()->setProperty ("version", 1);
  2407. info.getDynamicObject()->setProperty ("author", "xcode");
  2408. v->setProperty ("images", images);
  2409. v->setProperty ("info", info);
  2410. return JSON::toString (var (v.get()));
  2411. }
  2412. void createXcassetsFolderFromIcons() const
  2413. {
  2414. const File assets (getTargetFolder().getChildFile (project.getProjectFilenameRoot())
  2415. .getChildFile ("Images.xcassets"));
  2416. const File iconSet (assets.getChildFile ("AppIcon.appiconset"));
  2417. const File launchImage (assets.getChildFile ("LaunchImage.launchimage"));
  2418. overwriteFileIfDifferentOrThrow (iconSet.getChildFile ("Contents.json"), getiOSAppIconContents());
  2419. createiOSIconFiles (iconSet);
  2420. overwriteFileIfDifferentOrThrow (launchImage.getChildFile ("Contents.json"), getiOSLaunchImageContents());
  2421. createiOSLaunchImageFiles (launchImage);
  2422. RelativePath assetsPath (assets, getTargetFolder(), RelativePath::buildTargetFolder);
  2423. addFileReference (assetsPath.toUnixStyle());
  2424. resourceIDs.add (addBuildFile (assetsPath, false, false));
  2425. resourceFileRefs.add (createFileRefID (assetsPath));
  2426. }
  2427. //==============================================================================
  2428. static String indentBracedList (const StringArray& list) { return "{" + indentList (list, ";", 0, true) + " }"; }
  2429. static String indentParenthesisedList (const StringArray& list) { return "(" + indentList (list, ",", 1, false) + " )"; }
  2430. static String indentList (const StringArray& list, const String& separator, int extraTabs, bool shouldSort)
  2431. {
  2432. if (list.size() == 0)
  2433. return " ";
  2434. const String tabs ("\n" + String::repeatedString ("\t", extraTabs + 4));
  2435. if (shouldSort)
  2436. {
  2437. StringArray sorted (list);
  2438. sorted.sort (true);
  2439. return tabs + sorted.joinIntoString (separator + tabs) + separator;
  2440. }
  2441. return tabs + list.joinIntoString (separator + tabs) + separator;
  2442. }
  2443. String createID (String rootString) const
  2444. {
  2445. if (rootString.startsWith ("${"))
  2446. rootString = rootString.fromFirstOccurrenceOf ("}/", false, false);
  2447. rootString += project.getProjectUID();
  2448. return MD5 (rootString.toUTF8()).toHexString().substring (0, 24).toUpperCase();
  2449. }
  2450. String createFileRefID (const RelativePath& path) const { return createFileRefID (path.toUnixStyle()); }
  2451. String createFileRefID (const String& path) const { return createID ("__fileref_" + path); }
  2452. String getIDForGroup (const Project::Item& item) const { return createID (item.getID()); }
  2453. bool shouldFileBeCompiledByDefault (const RelativePath& file) const override
  2454. {
  2455. return file.hasFileExtension (sourceFileExtensions);
  2456. }
  2457. static String getOSXVersionName (int version)
  2458. {
  2459. jassert (version >= 4);
  2460. return "10." + String (version);
  2461. }
  2462. static String getSDKName (int version)
  2463. {
  2464. return getOSXVersionName (version) + " SDK";
  2465. }
  2466. JUCE_DECLARE_NON_COPYABLE (XcodeProjectExporter)
  2467. };