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.

536 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-10 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  18. #ifndef __JUCER_PROJECTSAVER_JUCEHEADER__
  19. #define __JUCER_PROJECTSAVER_JUCEHEADER__
  20. //==============================================================================
  21. class ProjectSaver
  22. {
  23. public:
  24. ProjectSaver (Project& project_, const File& projectFile_)
  25. : project (project_), projectFile (projectFile_), resourceFile (project_),
  26. generatedCodeFolder (project.getGeneratedCodeFolder())
  27. {
  28. }
  29. String save()
  30. {
  31. const File oldFile (project.getFile());
  32. project.setFile (projectFile);
  33. const String linkageMode (project.getJuceLinkageMode());
  34. if (linkageMode == Project::notLinkedToJuce)
  35. {
  36. hasAppHeaderFile = ! project.getProjectType().isLibrary();
  37. numJuceSourceFiles = 0;
  38. }
  39. else if (linkageMode == Project::useAmalgamatedJuce
  40. || linkageMode == Project::useAmalgamatedJuceViaSingleTemplate)
  41. {
  42. hasAppHeaderFile = true;
  43. numJuceSourceFiles = 1;
  44. }
  45. else if (linkageMode == Project::useAmalgamatedJuceViaMultipleTemplates)
  46. {
  47. hasAppHeaderFile = true;
  48. numJuceSourceFiles = project.getNumSeparateAmalgamatedFiles();
  49. }
  50. else if (linkageMode == Project::useLinkedJuce)
  51. {
  52. hasAppHeaderFile = true;
  53. numJuceSourceFiles = 0;
  54. }
  55. else
  56. {
  57. jassertfalse;
  58. }
  59. hasResources = (resourceFile.getNumFiles() > 0);
  60. writeMainProjectFile();
  61. if (! generatedCodeFolder.createDirectory())
  62. errors.add ("Couldn't create folder: " + generatedCodeFolder.getFullPathName());
  63. if (errors.size() == 0)
  64. writeAppConfigFile();
  65. if (errors.size() == 0)
  66. writeJuceSourceWrappers();
  67. if (errors.size() == 0)
  68. writeProjects();
  69. if (errors.size() > 0)
  70. project.setFile (oldFile);
  71. return errors[0];
  72. }
  73. bool saveGeneratedFile (const String& filePath, const MemoryOutputStream& newData)
  74. {
  75. return replaceFileIfDifferent (generatedCodeFolder.getChildFile (filePath), newData);
  76. }
  77. private:
  78. Project& project;
  79. const File& projectFile;
  80. ResourceFile resourceFile;
  81. File generatedCodeFolder;
  82. StringArray errors;
  83. File appConfigFile, juceHeaderFile, binaryDataCpp, pluginCharacteristicsFile;
  84. bool hasAppHeaderFile, hasResources;
  85. int numJuceSourceFiles;
  86. void writeMainProjectFile()
  87. {
  88. ScopedPointer <XmlElement> xml (project.getProjectRoot().createXml());
  89. jassert (xml != nullptr);
  90. if (xml != nullptr)
  91. {
  92. #if JUCE_DEBUG
  93. {
  94. MemoryOutputStream mo;
  95. project.getProjectRoot().writeToStream (mo);
  96. MemoryInputStream mi (mo.getData(), mo.getDataSize(), false);
  97. ValueTree v = ValueTree::readFromStream (mi);
  98. ScopedPointer <XmlElement> xml2 (v.createXml());
  99. // This bit just tests that ValueTree save/load works reliably.. Let me know if this asserts for you!
  100. jassert (xml->isEquivalentTo (xml2, true));
  101. }
  102. #endif
  103. MemoryOutputStream mo;
  104. xml->writeToStream (mo, String::empty);
  105. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (projectFile, mo))
  106. errors.add ("Couldn't write to the target file!");
  107. }
  108. }
  109. static void writeJucerComment (OutputStream& out)
  110. {
  111. out << "/*" << newLine << newLine
  112. << " IMPORTANT! This file is auto-generated each time you save your" << newLine
  113. << " project - if you alter its contents, your changes may be overwritten!" << newLine
  114. << newLine;
  115. }
  116. bool writeAppConfig (OutputStream& out)
  117. {
  118. writeJucerComment (out);
  119. out << " If you want to change any of these values, use the Introjucer to do so, rather than" << newLine
  120. << " editing this file directly!" << newLine
  121. << newLine
  122. << " Any commented-out settings will fall back to using the default values that" << newLine
  123. << " they are given in juce_Config.h" << newLine
  124. << newLine
  125. << "*/" << newLine << newLine;
  126. bool notActive = project.getJuceLinkageMode() == Project::useLinkedJuce
  127. || project.getJuceLinkageMode() == Project::notLinkedToJuce;
  128. if (notActive)
  129. out << "/* NOTE: These configs aren't available when you're linking to the juce library statically!" << newLine
  130. << " If you need to set a configuration that differs from the default, you'll need" << newLine
  131. << " to include the amalgamated Juce files." << newLine << newLine;
  132. OwnedArray <Project::ConfigFlag> flags;
  133. project.getAllConfigFlags (flags);
  134. for (int i = 0; i < flags.size(); ++i)
  135. {
  136. const Project::ConfigFlag* const f = flags[i];
  137. const String value (f->value.toString());
  138. if (value != Project::configFlagEnabled && value != Project::configFlagDisabled)
  139. out << "//#define ";
  140. else
  141. out << "#define ";
  142. out << f->symbol;
  143. if (value == Project::configFlagEnabled)
  144. out << " 1";
  145. else if (value == Project::configFlagDisabled)
  146. out << " 0";
  147. out << newLine;
  148. }
  149. if (notActive)
  150. out << newLine << "*/" << newLine;
  151. return flags.size() > 0;
  152. }
  153. void writeSourceWrapper (OutputStream& out, int fileNumber)
  154. {
  155. writeJucerComment (out);
  156. out << " This file pulls in all the Juce source code, and builds it using the settings" << newLine
  157. << " defined in " << appConfigFile.getFileName() << "." << newLine
  158. << newLine
  159. << " If you want to change the method by which Juce is linked into your app, use the" << newLine
  160. << " Jucer to change it, rather than trying to edit this file directly." << newLine
  161. << newLine
  162. << "*/"
  163. << newLine << newLine
  164. << CodeHelpers::createIncludeStatement (appConfigFile, appConfigFile) << newLine;
  165. if (fileNumber == 0)
  166. writeInclude (out, project.isUsingFullyAmalgamatedFile() ? "juce_amalgamated.cpp"
  167. : "amalgamation/juce_amalgamated_template.cpp");
  168. else
  169. writeInclude (out, "amalgamation/juce_amalgamated" + String (fileNumber) + ".cpp");
  170. }
  171. void writeAppHeader (OutputStream& out)
  172. {
  173. writeJucerComment (out);
  174. out << " This is the header file that your files should include in order to get all the" << newLine
  175. << " Juce library headers. You should NOT include juce.h or juce_amalgamated.h directly in" << newLine
  176. << " your own source files, because that wouldn't pick up the correct Juce configuration" << newLine
  177. << " options for your app." << newLine
  178. << newLine
  179. << "*/" << newLine << newLine;
  180. String headerGuard ("__APPHEADERFILE_" + String::toHexString (juceHeaderFile.hashCode()).toUpperCase() + "__");
  181. out << "#ifndef " << headerGuard << newLine
  182. << "#define " << headerGuard << newLine << newLine;
  183. if (appConfigFile.exists())
  184. out << CodeHelpers::createIncludeStatement (appConfigFile, appConfigFile) << newLine;
  185. if (project.getJuceLinkageMode() != Project::notLinkedToJuce)
  186. {
  187. writeInclude (out, (project.isUsingSingleTemplateFile() || project.isUsingMultipleTemplateFiles())
  188. ? "juce_amalgamated.h" // could use "amalgamation/juce_amalgamated_template.h", but it's slower..
  189. : (project.isUsingFullyAmalgamatedFile()
  190. ? "juce_amalgamated.h"
  191. : "juce.h"));
  192. }
  193. if (binaryDataCpp.exists())
  194. out << CodeHelpers::createIncludeStatement (binaryDataCpp.withFileExtension (".h"), appConfigFile) << newLine;
  195. out << newLine
  196. << "namespace ProjectInfo" << newLine
  197. << "{" << newLine
  198. << " const char* const projectName = " << CodeHelpers::addEscapeChars (project.getProjectName().toString()).quoted() << ";" << newLine
  199. << " const char* const versionString = " << CodeHelpers::addEscapeChars (project.getVersion().toString()).quoted() << ";" << newLine
  200. << " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine
  201. << "}" << newLine
  202. << newLine
  203. << "#endif // " << headerGuard << newLine;
  204. }
  205. void writeInclude (OutputStream& out, const String& pathFromJuceFolder)
  206. {
  207. StringArray paths, guards;
  208. for (int i = project.getNumExporters(); --i >= 0;)
  209. {
  210. ScopedPointer <ProjectExporter> exporter (project.createExporter (i));
  211. if (exporter != nullptr)
  212. {
  213. paths.add (exporter->getIncludePathForFileInJuceFolder (pathFromJuceFolder, juceHeaderFile));
  214. guards.add ("defined (" + exporter->getExporterIdentifierMacro() + ")");
  215. }
  216. }
  217. StringArray uniquePaths (paths);
  218. uniquePaths.removeDuplicates (false);
  219. if (uniquePaths.size() == 1)
  220. {
  221. out << "#include " << paths[0] << newLine;
  222. }
  223. else
  224. {
  225. int i = paths.size();
  226. for (; --i >= 0;)
  227. {
  228. for (int j = i; --j >= 0;)
  229. {
  230. if (paths[i] == paths[j] && guards[i] == guards[j])
  231. {
  232. paths.remove (i);
  233. guards.remove (i);
  234. }
  235. }
  236. }
  237. for (i = 0; i < paths.size(); ++i)
  238. {
  239. out << (i == 0 ? "#if " : "#elif ") << guards[i] << newLine
  240. << " #include " << paths[i] << newLine;
  241. }
  242. out << "#endif" << newLine;
  243. }
  244. }
  245. static int countMaxPluginChannels (const String& configString, bool isInput)
  246. {
  247. StringArray configs;
  248. configs.addTokens (configString, ", {}", String::empty);
  249. configs.trim();
  250. configs.removeEmptyStrings();
  251. jassert ((configs.size() & 1) == 0); // looks like a syntax error in the configs?
  252. int maxVal = 0;
  253. for (int i = (isInput ? 0 : 1); i < configs.size(); i += 2)
  254. maxVal = jmax (maxVal, configs[i].getIntValue());
  255. return maxVal;
  256. }
  257. static void writePluginCharacteristics (const File& destFile, Project& project, OutputStream& out)
  258. {
  259. String headerGuard ("__PLUGINCHARACTERISTICS_" + String::toHexString (destFile.hashCode()).toUpperCase() + "__");
  260. writeJucerComment (out);
  261. out << " This header file contains configuration options for the plug-in. If you need to change any of" << newLine
  262. << " these, it'd be wise to do so using the Jucer, rather than editing this file directly..." << newLine
  263. << newLine
  264. << "*/" << newLine
  265. << newLine
  266. << "#ifndef " << headerGuard << newLine
  267. << "#define " << headerGuard << newLine
  268. << newLine
  269. << "#define JucePlugin_Build_VST " << ((bool) project.shouldBuildVST().getValue() ? 1 : 0) << " // (If you change this value, you'll also need to re-export the projects using the Jucer)" << newLine
  270. << "#define JucePlugin_Build_AU " << ((bool) project.shouldBuildAU().getValue() ? 1 : 0) << " // (If you change this value, you'll also need to re-export the projects using the Jucer)" << newLine
  271. << "#define JucePlugin_Build_RTAS " << ((bool) project.shouldBuildRTAS().getValue() ? 1 : 0) << " // (If you change this value, you'll also need to re-export the projects using the Jucer)" << newLine
  272. << newLine
  273. << "#define JucePlugin_Name " << project.getPluginName().toString().quoted() << newLine
  274. << "#define JucePlugin_Desc " << project.getPluginDesc().toString().quoted() << newLine
  275. << "#define JucePlugin_Manufacturer " << project.getPluginManufacturer().toString().quoted() << newLine
  276. << "#define JucePlugin_ManufacturerCode '" << project.getPluginManufacturerCode().toString().trim().substring (0, 4) << "'" << newLine
  277. << "#define JucePlugin_PluginCode '" << project.getPluginCode().toString().trim().substring (0, 4) << "'" << newLine
  278. << "#define JucePlugin_MaxNumInputChannels " << countMaxPluginChannels (project.getPluginChannelConfigs().toString(), true) << newLine
  279. << "#define JucePlugin_MaxNumOutputChannels " << countMaxPluginChannels (project.getPluginChannelConfigs().toString(), false) << newLine
  280. << "#define JucePlugin_PreferredChannelConfigurations " << project.getPluginChannelConfigs().toString() << newLine
  281. << "#define JucePlugin_IsSynth " << ((bool) project.getPluginIsSynth().getValue() ? 1 : 0) << newLine
  282. << "#define JucePlugin_WantsMidiInput " << ((bool) project.getPluginWantsMidiInput().getValue() ? 1 : 0) << newLine
  283. << "#define JucePlugin_ProducesMidiOutput " << ((bool) project.getPluginProducesMidiOut().getValue() ? 1 : 0) << newLine
  284. << "#define JucePlugin_SilenceInProducesSilenceOut " << ((bool) project.getPluginSilenceInProducesSilenceOut().getValue() ? 1 : 0) << newLine
  285. << "#define JucePlugin_TailLengthSeconds " << (double) project.getPluginTailLengthSeconds().getValue() << newLine
  286. << "#define JucePlugin_EditorRequiresKeyboardFocus " << ((bool) project.getPluginEditorNeedsKeyFocus().getValue() ? 1 : 0) << newLine
  287. << "#define JucePlugin_VersionCode " << project.getVersionAsHex() << newLine
  288. << "#define JucePlugin_VersionString " << project.getVersion().toString().quoted() << newLine
  289. << "#define JucePlugin_VSTUniqueID JucePlugin_PluginCode" << newLine
  290. << "#define JucePlugin_VSTCategory " << ((bool) project.getPluginIsSynth().getValue() ? "kPlugCategSynth" : "kPlugCategEffect") << newLine
  291. << "#define JucePlugin_AUMainType " << ((bool) project.getPluginIsSynth().getValue() ? "kAudioUnitType_MusicDevice" : "kAudioUnitType_Effect") << newLine
  292. << "#define JucePlugin_AUSubType JucePlugin_PluginCode" << newLine
  293. << "#define JucePlugin_AUExportPrefix " << project.getPluginAUExportPrefix().toString() << newLine
  294. << "#define JucePlugin_AUExportPrefixQuoted " << project.getPluginAUExportPrefix().toString().quoted() << newLine
  295. << "#define JucePlugin_AUManufacturerCode JucePlugin_ManufacturerCode" << newLine
  296. << "#define JucePlugin_CFBundleIdentifier " << project.getBundleIdentifier().toString() << newLine
  297. << "#define JucePlugin_AUCocoaViewClassName " << project.getPluginAUCocoaViewClassName().toString() << newLine
  298. << "#define JucePlugin_RTASCategory " << ((bool) project.getPluginIsSynth().getValue() ? "ePlugInCategory_SWGenerators" : "ePlugInCategory_None") << newLine
  299. << "#define JucePlugin_RTASManufacturerCode JucePlugin_ManufacturerCode" << newLine
  300. << "#define JucePlugin_RTASProductId JucePlugin_PluginCode" << newLine;
  301. out << "#define JUCE_USE_VSTSDK_2_4 1" << newLine
  302. << newLine
  303. << "#endif // " << headerGuard << newLine;
  304. }
  305. bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData)
  306. {
  307. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (f, newData))
  308. {
  309. errors.add ("Can't write to file: " + f.getFullPathName());
  310. return false;
  311. }
  312. return true;
  313. }
  314. void writeAppConfigFile()
  315. {
  316. appConfigFile = project.getGeneratedCodeFolder().getChildFile (project.getAppConfigFilename());
  317. MemoryOutputStream mem;
  318. if (writeAppConfig (mem))
  319. replaceFileIfDifferent (appConfigFile, mem);
  320. else
  321. appConfigFile.deleteFile();
  322. }
  323. void writeJuceSourceWrappers()
  324. {
  325. juceHeaderFile = project.getAppIncludeFile();
  326. binaryDataCpp = generatedCodeFolder.getChildFile ("BinaryData.cpp");
  327. if (resourceFile.getNumFiles() > 0)
  328. {
  329. //resourceFile.setJuceHeaderToInclude (juceHeaderFile);
  330. resourceFile.setClassName ("BinaryData");
  331. if (! resourceFile.write (binaryDataCpp))
  332. errors.add ("Can't create binary resources file: " + binaryDataCpp.getFullPathName());
  333. }
  334. else
  335. {
  336. binaryDataCpp.deleteFile();
  337. binaryDataCpp.withFileExtension ("h").deleteFile();
  338. }
  339. if (project.getProjectType().isLibrary())
  340. return;
  341. if (project.getProjectType().isAudioPlugin())
  342. {
  343. MemoryOutputStream mem;
  344. pluginCharacteristicsFile = generatedCodeFolder.getChildFile (project.getPluginCharacteristicsFilename());
  345. writePluginCharacteristics (pluginCharacteristicsFile, project, mem);
  346. replaceFileIfDifferent (pluginCharacteristicsFile, mem);
  347. }
  348. for (int i = 0; i <= project.getNumSeparateAmalgamatedFiles(); ++i)
  349. {
  350. const File sourceWrapperCpp (getSourceWrapperCpp (i));
  351. const File sourceWrapperMM (sourceWrapperCpp.withFileExtension (".mm"));
  352. if (numJuceSourceFiles > 0
  353. && ((i == 0 && numJuceSourceFiles == 1) || (i != 0 && numJuceSourceFiles > 1)))
  354. {
  355. MemoryOutputStream mem;
  356. writeSourceWrapper (mem, i);
  357. replaceFileIfDifferent (sourceWrapperCpp, mem);
  358. replaceFileIfDifferent (sourceWrapperMM, mem);
  359. }
  360. else
  361. {
  362. sourceWrapperMM.deleteFile();
  363. sourceWrapperCpp.deleteFile();
  364. }
  365. }
  366. if (hasAppHeaderFile)
  367. {
  368. MemoryOutputStream mem;
  369. writeAppHeader (mem);
  370. replaceFileIfDifferent (juceHeaderFile, mem);
  371. }
  372. else
  373. {
  374. juceHeaderFile.deleteFile();
  375. }
  376. }
  377. Project::Item createLibraryFilesGroup (ProjectExporter& exporter)
  378. {
  379. Project::Item libraryFiles (Project::Item::createGroup (project, project.getJuceCodeGroupName()));
  380. if (appConfigFile.exists())
  381. libraryFiles.addFile (appConfigFile, -1);
  382. if (hasAppHeaderFile)
  383. libraryFiles.addFile (juceHeaderFile, -1);
  384. if (hasResources)
  385. {
  386. libraryFiles.addFile (binaryDataCpp, -1);
  387. libraryFiles.addFile (binaryDataCpp.withFileExtension (".h"), -1);
  388. }
  389. if (numJuceSourceFiles > 0)
  390. {
  391. for (int j = 0; j <= project.getNumSeparateAmalgamatedFiles(); ++j)
  392. {
  393. const File sourceWrapperCpp (getSourceWrapperCpp (j));
  394. const File sourceWrapperMM (sourceWrapperCpp.withFileExtension (".mm"));
  395. if ((j == 0 && numJuceSourceFiles == 1) || (j != 0 && numJuceSourceFiles > 1))
  396. {
  397. if (exporter.usesMMFiles())
  398. libraryFiles.addFile (sourceWrapperMM, -1);
  399. else
  400. libraryFiles.addFile (sourceWrapperCpp, -1);
  401. }
  402. }
  403. }
  404. if (project.getProjectType().isAudioPlugin())
  405. libraryFiles.addFile (pluginCharacteristicsFile, -1);
  406. libraryFiles.setID ("__jucelibfiles");
  407. return libraryFiles;
  408. }
  409. void writeProjects()
  410. {
  411. for (int i = project.getNumExporters(); --i >= 0;)
  412. {
  413. ScopedPointer <ProjectExporter> exporter (project.createExporter (i));
  414. std::cout << "Writing files for: " << exporter->getName() << std::endl;
  415. const File targetFolder (exporter->getTargetFolder());
  416. if (targetFolder.createDirectory())
  417. {
  418. exporter->generatedGroups.add (createLibraryFilesGroup (*exporter));
  419. for (int j = 0; j < exporter->libraryModules.size(); ++j)
  420. exporter->libraryModules.getUnchecked(j)->addExtraCodeGroups (*exporter, exporter->generatedGroups);
  421. try
  422. {
  423. exporter->create();
  424. }
  425. catch (ProjectExporter::SaveError& error)
  426. {
  427. errors.add (error.message);
  428. }
  429. }
  430. else
  431. {
  432. errors.add ("Can't create folder: " + exporter->getTargetFolder().getFullPathName());
  433. }
  434. }
  435. }
  436. File getSourceWrapperCpp (int fileIndex) const
  437. {
  438. return project.getGeneratedCodeFolder()
  439. .getChildFile (project.getJuceSourceFilenameRoot() + (fileIndex != 0 ? String (fileIndex) : String::empty))
  440. .withFileExtension (".cpp");
  441. }
  442. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver);
  443. };
  444. #endif