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.

545 lines
24KB

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