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.

361 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. replaceFileIfDifferent (projectFile, mo);
  96. }
  97. }
  98. bool writeAppConfig (OutputStream& out)
  99. {
  100. writeAutoGenWarningComment (out);
  101. out << " If you want to change any of these values, use the Introjucer to do so, rather than" << newLine
  102. << " editing this file directly!" << newLine
  103. << newLine
  104. << " Any commented-out settings will fall back to using the default values that" << newLine
  105. << " they are given in juce_Config.h" << newLine
  106. << newLine
  107. << "*/" << newLine << newLine;
  108. bool notActive = project.getJuceLinkageMode() == Project::useLinkedJuce
  109. || project.getJuceLinkageMode() == Project::notLinkedToJuce;
  110. if (notActive)
  111. out << "/* NOTE: These configs aren't available when you're linking to the juce library statically!" << newLine
  112. << " If you need to set a configuration that differs from the default, you'll need" << newLine
  113. << " to include the amalgamated Juce files." << newLine << newLine;
  114. OwnedArray <Project::ConfigFlag> flags;
  115. project.getAllConfigFlags (flags);
  116. for (int i = 0; i < flags.size(); ++i)
  117. {
  118. const Project::ConfigFlag* const f = flags[i];
  119. const String value (f->value.toString());
  120. if (value != Project::configFlagEnabled && value != Project::configFlagDisabled)
  121. out << "//#define ";
  122. else
  123. out << "#define ";
  124. out << f->symbol;
  125. if (value == Project::configFlagEnabled)
  126. out << " 1";
  127. else if (value == Project::configFlagDisabled)
  128. out << " 0";
  129. out << newLine;
  130. }
  131. if (notActive)
  132. out << newLine << "*/" << newLine;
  133. return flags.size() > 0;
  134. }
  135. void writeAppConfigFile()
  136. {
  137. appConfigFile = project.getGeneratedCodeFolder().getChildFile (project.getAppConfigFilename());
  138. MemoryOutputStream mem;
  139. if (writeAppConfig (mem))
  140. saveGeneratedFile (project.getAppConfigFilename(), mem);
  141. else
  142. appConfigFile.deleteFile();
  143. }
  144. void writeAppHeader (OutputStream& out)
  145. {
  146. writeAutoGenWarningComment (out);
  147. out << " This is the header file that your files should include in order to get all the" << newLine
  148. << " Juce library headers. You should NOT include juce.h or juce_amalgamated.h directly in" << newLine
  149. << " your own source files, because that wouldn't pick up the correct Juce configuration" << newLine
  150. << " options for your app." << newLine
  151. << newLine
  152. << "*/" << newLine << newLine;
  153. String headerGuard ("__APPHEADERFILE_" + project.getProjectUID().toUpperCase() + "__");
  154. out << "#ifndef " << headerGuard << newLine
  155. << "#define " << headerGuard << newLine << newLine;
  156. if (appConfigFile.exists())
  157. out << CodeHelpers::createIncludeStatement (project.getAppConfigFilename()) << newLine;
  158. {
  159. OwnedArray<LibraryModule> modules;
  160. project.getProjectType().createRequiredModules (project, modules);
  161. StringArray paths, guards;
  162. for (int i = 0; i < modules.size(); ++i)
  163. modules.getUnchecked(i)->getHeaderFiles (project, paths, guards);
  164. StringArray uniquePaths (paths);
  165. uniquePaths.removeDuplicates (false);
  166. if (uniquePaths.size() == 1)
  167. {
  168. out << "#include " << paths[0] << newLine;
  169. }
  170. else
  171. {
  172. int i = paths.size();
  173. for (; --i >= 0;)
  174. {
  175. for (int j = i; --j >= 0;)
  176. {
  177. if (paths[i] == paths[j] && guards[i] == guards[j])
  178. {
  179. paths.remove (i);
  180. guards.remove (i);
  181. }
  182. }
  183. }
  184. for (i = 0; i < paths.size(); ++i)
  185. {
  186. out << (i == 0 ? "#if " : "#elif ") << guards[i] << newLine
  187. << " #include " << paths[i] << newLine;
  188. }
  189. out << "#endif" << newLine;
  190. }
  191. }
  192. if (binaryDataCpp.exists())
  193. out << CodeHelpers::createIncludeStatement (binaryDataCpp.withFileExtension (".h"), appConfigFile) << newLine;
  194. out << newLine
  195. << "namespace ProjectInfo" << newLine
  196. << "{" << newLine
  197. << " const char* const projectName = " << CodeHelpers::addEscapeChars (project.getProjectName().toString()).quoted() << ";" << newLine
  198. << " const char* const versionString = " << CodeHelpers::addEscapeChars (project.getVersion().toString()).quoted() << ";" << newLine
  199. << " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine
  200. << "}" << newLine
  201. << newLine
  202. << "#endif // " << headerGuard << newLine;
  203. }
  204. void writeAppHeader()
  205. {
  206. if (project.getJuceLinkageMode() != Project::notLinkedToJuce
  207. || ! project.getProjectType().isLibrary())
  208. {
  209. MemoryOutputStream mem;
  210. writeAppHeader (mem);
  211. saveGeneratedFile (project.getJuceSourceHFilename(), mem);
  212. }
  213. else
  214. {
  215. project.getAppIncludeFile().deleteFile();
  216. }
  217. }
  218. void writeBinaryDataFiles()
  219. {
  220. binaryDataCpp = project.getGeneratedCodeFolder().getChildFile ("BinaryData.cpp");
  221. ResourceFile resourceFile (project);
  222. if (resourceFile.getNumFiles() > 0)
  223. {
  224. resourceFile.setClassName ("BinaryData");
  225. if (resourceFile.write (binaryDataCpp))
  226. {
  227. generatedFilesGroup.addFile (binaryDataCpp, -1);
  228. generatedFilesGroup.addFile (binaryDataCpp.withFileExtension (".h"), -1);
  229. }
  230. else
  231. {
  232. errors.add ("Can't create binary resources file: " + binaryDataCpp.getFullPathName());
  233. }
  234. }
  235. else
  236. {
  237. binaryDataCpp.deleteFile();
  238. binaryDataCpp.withFileExtension ("h").deleteFile();
  239. }
  240. }
  241. void writeProjects()
  242. {
  243. for (int i = project.getNumExporters(); --i >= 0;)
  244. {
  245. ScopedPointer <ProjectExporter> exporter (project.createExporter (i));
  246. std::cout << "Writing files for: " << exporter->getName() << std::endl;
  247. if (exporter->getTargetFolder().createDirectory())
  248. {
  249. // start with a copy of the basic files, as each exporter may modify it.
  250. const ValueTree generatedGroupCopy (generatedFilesGroup.getNode().createCopy());
  251. int j;
  252. for (j = 0; j < exporter->libraryModules.size(); ++j)
  253. exporter->libraryModules.getUnchecked(j)->createFiles (*exporter, *this);
  254. exporter->generatedGroups.add (generatedFilesGroup);
  255. for (j = 0; j < exporter->libraryModules.size(); ++j)
  256. exporter->libraryModules.getUnchecked(j)->addExtraCodeGroups (*exporter, exporter->generatedGroups);
  257. try
  258. {
  259. exporter->create();
  260. }
  261. catch (ProjectExporter::SaveError& error)
  262. {
  263. errors.add (error.message);
  264. }
  265. generatedFilesGroup.getNode() = generatedGroupCopy;
  266. }
  267. else
  268. {
  269. errors.add ("Can't create folder: " + exporter->getTargetFolder().getFullPathName());
  270. }
  271. }
  272. }
  273. bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData)
  274. {
  275. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (f, newData))
  276. {
  277. errors.add ("Can't write to file: " + f.getFullPathName());
  278. return false;
  279. }
  280. return true;
  281. }
  282. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver);
  283. };
  284. #endif