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.

372 lines
13KB

  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. #include "jucer_ResourceFile.h"
  21. //==============================================================================
  22. class ProjectSaver
  23. {
  24. public:
  25. ProjectSaver (Project& project_, const File& projectFile_)
  26. : project (project_), projectFile (projectFile_),
  27. generatedFilesGroup (Project::Item::createGroup (project, project.getJuceCodeGroupName()))
  28. {
  29. generatedFilesGroup.setID ("__jucelibfiles");
  30. }
  31. Project& getProject() noexcept { return project; }
  32. String save()
  33. {
  34. jassert (generatedFilesGroup.getNumChildren() == 0); // this method can't be called more than once!
  35. const File oldFile (project.getFile());
  36. project.setFile (projectFile);
  37. writeMainProjectFile();
  38. if (! project.getGeneratedCodeFolder().createDirectory())
  39. errors.add ("Couldn't create folder: " + project.getGeneratedCodeFolder().getFullPathName());
  40. if (errors.size() == 0)
  41. writeAppConfigFile();
  42. if (errors.size() == 0)
  43. writeBinaryDataFiles();
  44. if (errors.size() == 0)
  45. writeAppHeader();
  46. if (errors.size() == 0)
  47. writeProjects();
  48. if (errors.size() > 0)
  49. project.setFile (oldFile);
  50. return errors[0];
  51. }
  52. bool saveGeneratedFile (const String& filePath, const MemoryOutputStream& newData)
  53. {
  54. const File file (project.getGeneratedCodeFolder().getChildFile (filePath));
  55. if (replaceFileIfDifferent (file, newData))
  56. {
  57. if (! generatedFilesGroup.findItemForFile (file).isValid())
  58. generatedFilesGroup.addFile (file, -1);
  59. return true;
  60. }
  61. return false;
  62. }
  63. static void writeAutoGenWarningComment (OutputStream& out)
  64. {
  65. out << "/*" << newLine << newLine
  66. << " IMPORTANT! This file is auto-generated each time you save your" << newLine
  67. << " project - if you alter its contents, your changes may be overwritten!" << newLine
  68. << newLine;
  69. }
  70. private:
  71. Project& project;
  72. const File projectFile;
  73. Project::Item generatedFilesGroup;
  74. StringArray errors;
  75. File appConfigFile, binaryDataCpp;
  76. void writeMainProjectFile()
  77. {
  78. ScopedPointer <XmlElement> xml (project.getProjectRoot().createXml());
  79. jassert (xml != nullptr);
  80. if (xml != nullptr)
  81. {
  82. #if JUCE_DEBUG
  83. {
  84. MemoryOutputStream mo;
  85. project.getProjectRoot().writeToStream (mo);
  86. MemoryInputStream mi (mo.getData(), mo.getDataSize(), false);
  87. ValueTree v = ValueTree::readFromStream (mi);
  88. ScopedPointer <XmlElement> xml2 (v.createXml());
  89. // This bit just tests that ValueTree save/load works reliably.. Let me know if this asserts for you!
  90. jassert (xml->isEquivalentTo (xml2, true));
  91. }
  92. #endif
  93. MemoryOutputStream mo;
  94. xml->writeToStream (mo, String::empty);
  95. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (projectFile, mo))
  96. errors.add ("Couldn't write to the target file!");
  97. }
  98. }
  99. bool writeAppConfig (OutputStream& out)
  100. {
  101. writeAutoGenWarningComment (out);
  102. out << " If you want to change any of these values, use the Introjucer to do so, rather than" << newLine
  103. << " editing this file directly!" << newLine
  104. << newLine
  105. << " Any commented-out settings will fall back to using the default values that" << newLine
  106. << " they are given in juce_Config.h" << newLine
  107. << newLine
  108. << "*/" << newLine << newLine;
  109. bool notActive = project.getJuceLinkageMode() == Project::useLinkedJuce
  110. || project.getJuceLinkageMode() == Project::notLinkedToJuce;
  111. if (notActive)
  112. out << "/* NOTE: These configs aren't available when you're linking to the juce library statically!" << newLine
  113. << " If you need to set a configuration that differs from the default, you'll need" << newLine
  114. << " to include the amalgamated Juce files." << newLine << newLine;
  115. OwnedArray <Project::ConfigFlag> flags;
  116. project.getAllConfigFlags (flags);
  117. for (int i = 0; i < flags.size(); ++i)
  118. {
  119. const Project::ConfigFlag* const f = flags[i];
  120. const String value (f->value.toString());
  121. if (value != Project::configFlagEnabled && value != Project::configFlagDisabled)
  122. out << "//#define ";
  123. else
  124. out << "#define ";
  125. out << f->symbol;
  126. if (value == Project::configFlagEnabled)
  127. out << " 1";
  128. else if (value == Project::configFlagDisabled)
  129. out << " 0";
  130. out << newLine;
  131. }
  132. if (notActive)
  133. out << newLine << "*/" << newLine;
  134. return flags.size() > 0;
  135. }
  136. void writeAppConfigFile()
  137. {
  138. appConfigFile = project.getGeneratedCodeFolder().getChildFile (project.getAppConfigFilename());
  139. MemoryOutputStream mem;
  140. if (writeAppConfig (mem))
  141. saveGeneratedFile (project.getAppConfigFilename(), mem);
  142. else
  143. appConfigFile.deleteFile();
  144. }
  145. void writeAppHeader (OutputStream& out)
  146. {
  147. writeAutoGenWarningComment (out);
  148. out << " This is the header file that your files should include in order to get all the" << newLine
  149. << " Juce library headers. You should NOT include juce.h or juce_amalgamated.h directly in" << newLine
  150. << " your own source files, because that wouldn't pick up the correct Juce configuration" << newLine
  151. << " options for your app." << newLine
  152. << newLine
  153. << "*/" << newLine << newLine;
  154. String headerGuard ("__APPHEADERFILE_" + project.getProjectUID().toUpperCase() + "__");
  155. out << "#ifndef " << headerGuard << newLine
  156. << "#define " << headerGuard << newLine << newLine;
  157. if (appConfigFile.exists())
  158. out << CodeHelpers::createIncludeStatement (project.getAppConfigFilename()) << newLine;
  159. {
  160. OwnedArray<LibraryModule> modules;
  161. project.getProjectType().createRequiredModules (project, modules);
  162. StringArray paths, guards;
  163. for (int i = 0; i < modules.size(); ++i)
  164. modules.getUnchecked(i)->getHeaderFiles (project, paths, guards);
  165. StringArray uniquePaths (paths);
  166. uniquePaths.removeDuplicates (false);
  167. if (uniquePaths.size() == 1)
  168. {
  169. out << "#include " << paths[0] << newLine;
  170. }
  171. else
  172. {
  173. int i = paths.size();
  174. for (; --i >= 0;)
  175. {
  176. for (int j = i; --j >= 0;)
  177. {
  178. if (paths[i] == paths[j] && guards[i] == guards[j])
  179. {
  180. paths.remove (i);
  181. guards.remove (i);
  182. }
  183. }
  184. }
  185. for (i = 0; i < paths.size(); ++i)
  186. {
  187. out << (i == 0 ? "#if " : "#elif ") << guards[i] << newLine
  188. << " #include " << paths[i] << newLine;
  189. }
  190. out << "#endif" << newLine;
  191. }
  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 writeAppHeader()
  206. {
  207. if (project.getJuceLinkageMode() != Project::notLinkedToJuce
  208. || ! project.getProjectType().isLibrary())
  209. {
  210. MemoryOutputStream mem;
  211. writeAppHeader (mem);
  212. saveGeneratedFile (project.getJuceSourceHFilename(), mem);
  213. }
  214. else
  215. {
  216. project.getAppIncludeFile().deleteFile();
  217. }
  218. }
  219. bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData)
  220. {
  221. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (f, newData))
  222. {
  223. errors.add ("Can't write to file: " + f.getFullPathName());
  224. return false;
  225. }
  226. return true;
  227. }
  228. void writeBinaryDataFiles()
  229. {
  230. binaryDataCpp = project.getGeneratedCodeFolder().getChildFile ("BinaryData.cpp");
  231. ResourceFile resourceFile (project);
  232. if (resourceFile.getNumFiles() > 0)
  233. {
  234. resourceFile.setClassName ("BinaryData");
  235. if (resourceFile.write (binaryDataCpp))
  236. {
  237. generatedFilesGroup.addFile (binaryDataCpp, -1);
  238. generatedFilesGroup.addFile (binaryDataCpp.withFileExtension (".h"), -1);
  239. }
  240. else
  241. {
  242. errors.add ("Can't create binary resources file: " + binaryDataCpp.getFullPathName());
  243. }
  244. }
  245. else
  246. {
  247. binaryDataCpp.deleteFile();
  248. binaryDataCpp.withFileExtension ("h").deleteFile();
  249. }
  250. }
  251. void writeProjects()
  252. {
  253. for (int i = project.getNumExporters(); --i >= 0;)
  254. {
  255. ScopedPointer <ProjectExporter> exporter (project.createExporter (i));
  256. std::cout << "Writing files for: " << exporter->getName() << std::endl;
  257. const File targetFolder (exporter->getTargetFolder());
  258. if (targetFolder.createDirectory())
  259. {
  260. // start with a copy of the basic files, as each exporter may modify it.
  261. const ValueTree generatedGroupCopy (generatedFilesGroup.getNode().createCopy());
  262. int j;
  263. for (j = 0; j < exporter->libraryModules.size(); ++j)
  264. exporter->libraryModules.getUnchecked(j)->createFiles (*exporter, *this);
  265. exporter->generatedGroups.add (generatedFilesGroup);
  266. for (j = 0; j < exporter->libraryModules.size(); ++j)
  267. exporter->libraryModules.getUnchecked(j)->addExtraCodeGroups (*exporter, exporter->generatedGroups);
  268. try
  269. {
  270. exporter->create();
  271. }
  272. catch (ProjectExporter::SaveError& error)
  273. {
  274. errors.add (error.message);
  275. }
  276. generatedFilesGroup.getNode() = generatedGroupCopy;
  277. }
  278. else
  279. {
  280. errors.add ("Can't create folder: " + exporter->getTargetFolder().getFullPathName());
  281. }
  282. }
  283. }
  284. File getSourceWrapperCpp (int fileIndex) const
  285. {
  286. return project.getGeneratedCodeFolder()
  287. .getChildFile (project.getJuceSourceFilenameRoot() + (fileIndex != 0 ? String (fileIndex) : String::empty))
  288. .withFileExtension (".cpp");
  289. }
  290. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver);
  291. };
  292. #endif