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.

2890 lines
128KB

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