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.

2652 lines
116KB

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