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.

615 lines
22KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 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. #include "../Project/jucer_Module.h"
  22. #include "jucer_ProjectExporter.h"
  23. //==============================================================================
  24. class ProjectSaver
  25. {
  26. public:
  27. ProjectSaver (Project& p, const File& file)
  28. : project (p),
  29. projectFile (file),
  30. generatedCodeFolder (project.getGeneratedCodeFolder()),
  31. generatedFilesGroup (Project::Item::createGroup (project, getJuceCodeGroupName(), "__generatedcode__")),
  32. hasBinaryData (false)
  33. {
  34. generatedFilesGroup.setID (getGeneratedGroupID());
  35. }
  36. Project& getProject() noexcept { return project; }
  37. struct SaveThread : public ThreadWithProgressWindow
  38. {
  39. public:
  40. SaveThread (ProjectSaver& ps)
  41. : ThreadWithProgressWindow ("Saving...", true, false),
  42. saver (ps), result (Result::ok())
  43. {}
  44. void run()
  45. {
  46. setProgress (-1);
  47. result = saver.save (false);
  48. }
  49. ProjectSaver& saver;
  50. Result result;
  51. JUCE_DECLARE_NON_COPYABLE (SaveThread)
  52. };
  53. Result save (bool showProgressBox)
  54. {
  55. if (showProgressBox)
  56. {
  57. SaveThread thread (*this);
  58. thread.runThread();
  59. return thread.result;
  60. }
  61. const String appConfigUserContent (loadUserContentFromAppConfig());
  62. const File oldFile (project.getFile());
  63. project.setFile (projectFile);
  64. writeMainProjectFile();
  65. OwnedArray<LibraryModule> modules;
  66. {
  67. ModuleList moduleList;
  68. Result scanResult (moduleList.rescan (ModuleList::getDefaultModulesFolder (&project)));
  69. if (scanResult.failed())
  70. return scanResult;
  71. project.createRequiredModules (moduleList, modules);
  72. }
  73. if (errors.size() == 0) writeAppConfigFile (modules, appConfigUserContent);
  74. if (errors.size() == 0) writeBinaryDataFiles();
  75. if (errors.size() == 0) writeAppHeader (modules);
  76. if (errors.size() == 0) writeProjects (modules);
  77. if (errors.size() == 0) writeAppConfigFile (modules, appConfigUserContent); // (this is repeated in case the projects added anything to it)
  78. if (errors.size() == 0 && generatedCodeFolder.exists())
  79. writeReadmeFile();
  80. if (generatedCodeFolder.exists())
  81. deleteUnwantedFilesIn (generatedCodeFolder);
  82. if (errors.size() > 0)
  83. {
  84. project.setFile (oldFile);
  85. return Result::fail (errors[0]);
  86. }
  87. return Result::ok();
  88. }
  89. Result saveResourcesOnly()
  90. {
  91. writeBinaryDataFiles();
  92. if (errors.size() > 0)
  93. return Result::fail (errors[0]);
  94. return Result::ok();
  95. }
  96. Project::Item saveGeneratedFile (const String& filePath, const MemoryOutputStream& newData)
  97. {
  98. if (! generatedCodeFolder.createDirectory())
  99. {
  100. addError ("Couldn't create folder: " + generatedCodeFolder.getFullPathName());
  101. return Project::Item (project, ValueTree::invalid);
  102. }
  103. const File file (generatedCodeFolder.getChildFile (filePath));
  104. if (replaceFileIfDifferent (file, newData))
  105. return addFileToGeneratedGroup (file);
  106. return Project::Item (project, ValueTree::invalid);
  107. }
  108. Project::Item addFileToGeneratedGroup (const File& file)
  109. {
  110. Project::Item item (generatedFilesGroup.findItemForFile (file));
  111. if (item.isValid())
  112. return item;
  113. generatedFilesGroup.addFile (file, -1, true);
  114. return generatedFilesGroup.findItemForFile (file);
  115. }
  116. void setExtraAppConfigFileContent (const String& content)
  117. {
  118. extraAppConfigContent = content;
  119. }
  120. static void writeAutoGenWarningComment (OutputStream& out)
  121. {
  122. out << "/*" << newLine << newLine
  123. << " IMPORTANT! This file is auto-generated each time you save your" << newLine
  124. << " project - if you alter its contents, your changes may be overwritten!" << newLine
  125. << newLine;
  126. }
  127. static const char* getGeneratedGroupID() noexcept { return "__jucelibfiles"; }
  128. Project::Item& getGeneratedCodeGroup() { return generatedFilesGroup; }
  129. static String getJuceCodeGroupName() { return "Juce Library Code"; }
  130. File getGeneratedCodeFolder() const { return generatedCodeFolder; }
  131. File getLocalModuleFolder (const LibraryModule& m) const { return generatedCodeFolder.getChildFile ("modules").getChildFile (m.getID()); }
  132. bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData)
  133. {
  134. filesCreated.add (f);
  135. if (! FileHelpers::overwriteFileWithNewDataIfDifferent (f, newData))
  136. {
  137. addError ("Can't write to file: " + f.getFullPathName());
  138. return false;
  139. }
  140. return true;
  141. }
  142. bool copyFolder (const File& source, const File& dest)
  143. {
  144. if (source.isDirectory() && dest.createDirectory())
  145. {
  146. Array<File> subFiles;
  147. source.findChildFiles (subFiles, File::findFiles, false);
  148. for (int i = 0; i < subFiles.size(); ++i)
  149. {
  150. const File target (dest.getChildFile (subFiles.getReference(i).getFileName()));
  151. filesCreated.add (target);
  152. if (! subFiles.getReference(i).copyFileTo (target))
  153. return false;
  154. }
  155. subFiles.clear();
  156. source.findChildFiles (subFiles, File::findDirectories, false);
  157. for (int i = 0; i < subFiles.size(); ++i)
  158. if (! copyFolder (subFiles.getReference(i), dest.getChildFile (subFiles.getReference(i).getFileName())))
  159. return false;
  160. return true;
  161. }
  162. return false;
  163. }
  164. private:
  165. Project& project;
  166. const File projectFile, generatedCodeFolder;
  167. Project::Item generatedFilesGroup;
  168. String extraAppConfigContent;
  169. StringArray errors;
  170. CriticalSection errorLock;
  171. File appConfigFile;
  172. SortedSet<File> filesCreated;
  173. bool hasBinaryData;
  174. // Recursively clears out any files in a folder that we didn't create, but avoids
  175. // any folders containing hidden files that might be used by version-control systems.
  176. bool deleteUnwantedFilesIn (const File& parent)
  177. {
  178. bool folderIsNowEmpty = true;
  179. DirectoryIterator i (parent, false, "*", File::findFilesAndDirectories);
  180. Array<File> filesToDelete;
  181. bool isFolder;
  182. while (i.next (&isFolder, nullptr, nullptr, nullptr, nullptr, nullptr))
  183. {
  184. const File f (i.getFile());
  185. if (filesCreated.contains (f) || shouldFileBeKept (f.getFileName()))
  186. {
  187. folderIsNowEmpty = false;
  188. }
  189. else if (isFolder)
  190. {
  191. if (deleteUnwantedFilesIn (f))
  192. filesToDelete.add (f);
  193. else
  194. folderIsNowEmpty = false;
  195. }
  196. else
  197. {
  198. filesToDelete.add (f);
  199. }
  200. }
  201. for (int j = filesToDelete.size(); --j >= 0;)
  202. filesToDelete.getReference(j).deleteRecursively();
  203. return folderIsNowEmpty;
  204. }
  205. static bool shouldFileBeKept (const String& filename)
  206. {
  207. const char* filesToKeep[] = { ".svn", ".cvs", "CMakeLists.txt" };
  208. for (int i = 0; i < numElementsInArray (filesToKeep); ++i)
  209. if (filename == filesToKeep[i])
  210. return true;
  211. return false;
  212. }
  213. void writeMainProjectFile()
  214. {
  215. ScopedPointer <XmlElement> xml (project.getProjectRoot().createXml());
  216. jassert (xml != nullptr);
  217. if (xml != nullptr)
  218. {
  219. MemoryOutputStream mo;
  220. xml->writeToStream (mo, String::empty);
  221. replaceFileIfDifferent (projectFile, mo);
  222. }
  223. }
  224. static int findLongestModuleName (const OwnedArray<LibraryModule>& modules)
  225. {
  226. int longest = 0;
  227. for (int i = modules.size(); --i >= 0;)
  228. longest = jmax (longest, modules.getUnchecked(i)->getID().length());
  229. return longest;
  230. }
  231. File getAppConfigFile() const { return generatedCodeFolder.getChildFile (project.getAppConfigFilename()); }
  232. String loadUserContentFromAppConfig() const
  233. {
  234. StringArray lines, userContent;
  235. lines.addLines (getAppConfigFile().loadFileAsString());
  236. bool foundCodeSection = false;
  237. for (int i = 0; i < lines.size(); ++i)
  238. {
  239. if (lines[i].contains ("[BEGIN_USER_CODE_SECTION]"))
  240. {
  241. for (int j = i + 1; j < lines.size() && ! lines[j].contains ("[END_USER_CODE_SECTION]"); ++j)
  242. userContent.add (lines[j]);
  243. foundCodeSection = true;
  244. break;
  245. }
  246. }
  247. if (! foundCodeSection)
  248. {
  249. userContent.add (String::empty);
  250. userContent.add ("// (You can add your own code in this section, and the Introjucer will not overwrite it)");
  251. userContent.add (String::empty);
  252. }
  253. return userContent.joinIntoString (newLine) + newLine;
  254. }
  255. void writeAppConfig (OutputStream& out, const OwnedArray<LibraryModule>& modules, const String& userContent)
  256. {
  257. writeAutoGenWarningComment (out);
  258. out << " There's a section below where you can add your own custom code safely, and the" << newLine
  259. << " Introjucer will preserve the contents of that block, but the best way to change" << newLine
  260. << " any of these definitions is by using the Introjucer's project settings." << newLine
  261. << newLine
  262. << " Any commented-out settings will assume their default values." << newLine
  263. << newLine
  264. << "*/" << newLine
  265. << newLine;
  266. const String headerGuard ("__JUCE_APPCONFIG_" + project.getProjectUID().toUpperCase() + "__");
  267. out << "#ifndef " << headerGuard << newLine
  268. << "#define " << headerGuard << newLine
  269. << newLine
  270. << "//==============================================================================" << newLine
  271. << "// [BEGIN_USER_CODE_SECTION]" << newLine
  272. << userContent
  273. << "// [END_USER_CODE_SECTION]" << newLine
  274. << newLine
  275. << "//==============================================================================" << newLine;
  276. const int longestName = findLongestModuleName (modules);
  277. for (int k = 0; k < modules.size(); ++k)
  278. {
  279. LibraryModule* const m = modules.getUnchecked(k);
  280. out << "#define JUCE_MODULE_AVAILABLE_" << m->getID()
  281. << String::repeatedString (" ", longestName + 5 - m->getID().length()) << " 1" << newLine;
  282. }
  283. out << newLine;
  284. for (int j = 0; j < modules.size(); ++j)
  285. {
  286. LibraryModule* const m = modules.getUnchecked(j);
  287. OwnedArray <Project::ConfigFlag> flags;
  288. m->getConfigFlags (project, flags);
  289. if (flags.size() > 0)
  290. {
  291. out << "//==============================================================================" << newLine
  292. << "// " << m->getID() << " flags:" << newLine
  293. << newLine;
  294. for (int i = 0; i < flags.size(); ++i)
  295. {
  296. flags.getUnchecked(i)->value.referTo (project.getConfigFlag (flags.getUnchecked(i)->symbol));
  297. const Project::ConfigFlag* const f = flags[i];
  298. const String value (project.getConfigFlag (f->symbol).toString());
  299. out << "#ifndef " << f->symbol << newLine;
  300. if (value == Project::configFlagEnabled)
  301. out << " #define " << f->symbol << " 1";
  302. else if (value == Project::configFlagDisabled)
  303. out << " #define " << f->symbol << " 0";
  304. else
  305. out << " //#define " << f->symbol;
  306. out << newLine
  307. << "#endif" << newLine
  308. << newLine;
  309. }
  310. }
  311. }
  312. if (extraAppConfigContent.isNotEmpty())
  313. out << newLine << extraAppConfigContent.trimEnd() << newLine;
  314. out << newLine
  315. << "#endif // " << headerGuard << newLine;
  316. }
  317. void writeAppConfigFile (const OwnedArray<LibraryModule>& modules, const String& userContent)
  318. {
  319. appConfigFile = getAppConfigFile();
  320. MemoryOutputStream mem;
  321. writeAppConfig (mem, modules, userContent);
  322. saveGeneratedFile (project.getAppConfigFilename(), mem);
  323. }
  324. void writeAppHeader (OutputStream& out, const OwnedArray<LibraryModule>& modules)
  325. {
  326. writeAutoGenWarningComment (out);
  327. out << " This is the header file that your files should include in order to get all the" << newLine
  328. << " JUCE library headers. You should avoid including the JUCE headers directly in" << newLine
  329. << " your own source files, because that wouldn't pick up the correct configuration" << newLine
  330. << " options for your app." << newLine
  331. << newLine
  332. << "*/" << newLine << newLine;
  333. String headerGuard ("__APPHEADERFILE_" + project.getProjectUID().toUpperCase() + "__");
  334. out << "#ifndef " << headerGuard << newLine
  335. << "#define " << headerGuard << newLine << newLine;
  336. if (appConfigFile.exists())
  337. out << CodeHelpers::createIncludeStatement (project.getAppConfigFilename()) << newLine;
  338. for (int i = 0; i < modules.size(); ++i)
  339. modules.getUnchecked(i)->writeIncludes (*this, out);
  340. if (hasBinaryData)
  341. out << CodeHelpers::createIncludeStatement (project.getBinaryDataHeaderFile(), appConfigFile) << newLine;
  342. out << newLine
  343. << "#if ! DONT_SET_USING_JUCE_NAMESPACE" << newLine
  344. << " // If your code uses a lot of JUCE classes, then this will obviously save you" << newLine
  345. << " // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE." << newLine
  346. << " using namespace juce;" << newLine
  347. << "#endif" << newLine
  348. << newLine
  349. << "namespace ProjectInfo" << newLine
  350. << "{" << newLine
  351. << " const char* const projectName = " << CodeHelpers::addEscapeChars (project.getTitle()).quoted() << ";" << newLine
  352. << " const char* const versionString = " << CodeHelpers::addEscapeChars (project.getVersionString()).quoted() << ";" << newLine
  353. << " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine
  354. << "}" << newLine
  355. << newLine
  356. << "#endif // " << headerGuard << newLine;
  357. }
  358. void writeAppHeader (const OwnedArray<LibraryModule>& modules)
  359. {
  360. MemoryOutputStream mem;
  361. writeAppHeader (mem, modules);
  362. saveGeneratedFile (project.getJuceSourceHFilename(), mem);
  363. }
  364. void writeBinaryDataFiles()
  365. {
  366. const File binaryDataH (project.getBinaryDataHeaderFile());
  367. ResourceFile resourceFile (project);
  368. if (resourceFile.getNumFiles() > 0)
  369. {
  370. resourceFile.setClassName ("BinaryData");
  371. Array<File> binaryDataFiles;
  372. int maxSize = project.getMaxBinaryFileSize().getValue();
  373. if (maxSize <= 0)
  374. maxSize = 10 * 1024 * 1024;
  375. if (resourceFile.write (binaryDataFiles, maxSize))
  376. {
  377. hasBinaryData = true;
  378. for (int i = 0; i < binaryDataFiles.size(); ++i)
  379. {
  380. const File& f = binaryDataFiles.getReference(i);
  381. filesCreated.add (f);
  382. generatedFilesGroup.addFile (f, -1, ! f.hasFileExtension (".h"));
  383. }
  384. }
  385. else
  386. {
  387. addError ("Can't create binary resources file: "
  388. + project.getBinaryDataCppFile(0).getFullPathName());
  389. }
  390. }
  391. else
  392. {
  393. for (int i = 20; --i >= 0;)
  394. project.getBinaryDataCppFile (i).deleteFile();
  395. binaryDataH.deleteFile();
  396. }
  397. }
  398. void writeReadmeFile()
  399. {
  400. MemoryOutputStream out;
  401. out << newLine
  402. << " Important Note!!" << newLine
  403. << " ================" << newLine
  404. << newLine
  405. << "The purpose of this folder is to contain files that are auto-generated by the Introjucer," << newLine
  406. << "and ALL files in this folder will be mercilessly DELETED and completely re-written whenever" << newLine
  407. << "the Introjucer saves your project." << newLine
  408. << newLine
  409. << "Therefore, it's a bad idea to make any manual changes to the files in here, or to" << newLine
  410. << "put any of your own files in here if you don't want to lose them. (Of course you may choose" << newLine
  411. << "to add the folder's contents to your version-control system so that you can re-merge your own" << newLine
  412. << "modifications after the Introjucer has saved its changes)." << newLine;
  413. replaceFileIfDifferent (generatedCodeFolder.getChildFile ("ReadMe.txt"), out);
  414. }
  415. static void sortGroupRecursively (Project::Item group)
  416. {
  417. group.sortAlphabetically (true);
  418. for (int i = group.getNumChildren(); --i >= 0;)
  419. sortGroupRecursively (group.getChild(i));
  420. }
  421. void addError (const String& message)
  422. {
  423. const ScopedLock sl (errorLock);
  424. errors.add (message);
  425. }
  426. void writeProjects (const OwnedArray<LibraryModule>& modules)
  427. {
  428. ThreadPool threadPool;
  429. // keep a copy of the basic generated files group, as each exporter may modify it.
  430. const ValueTree originalGeneratedGroup (generatedFilesGroup.state.createCopy());
  431. for (Project::ExporterIterator exporter (project); exporter.next();)
  432. {
  433. if (exporter->getTargetFolder().createDirectory())
  434. {
  435. exporter->copyMainGroupFromProject();
  436. exporter->settings = exporter->settings.createCopy();
  437. exporter->addToExtraSearchPaths (RelativePath ("JuceLibraryCode", RelativePath::projectFolder));
  438. generatedFilesGroup.state = originalGeneratedGroup.createCopy();
  439. project.getProjectType().prepareExporter (*exporter);
  440. for (int j = 0; j < modules.size(); ++j)
  441. modules.getUnchecked(j)->prepareExporter (*exporter, *this);
  442. sortGroupRecursively (generatedFilesGroup);
  443. exporter->getAllGroups().add (generatedFilesGroup);
  444. threadPool.addJob (new ExporterJob (*this, exporter.exporter.release(), modules), true);
  445. }
  446. else
  447. {
  448. addError ("Can't create folder: " + exporter->getTargetFolder().getFullPathName());
  449. }
  450. }
  451. while (threadPool.getNumJobs() > 0)
  452. Thread::sleep (10);
  453. }
  454. class ExporterJob : public ThreadPoolJob
  455. {
  456. public:
  457. ExporterJob (ProjectSaver& ps, ProjectExporter* pe,
  458. const OwnedArray<LibraryModule>& moduleList)
  459. : ThreadPoolJob ("export"),
  460. owner (ps), exporter (pe), modules (moduleList)
  461. {
  462. }
  463. JobStatus runJob()
  464. {
  465. try
  466. {
  467. exporter->create (modules);
  468. std::cout << "Finished saving: " << exporter->getName() << std::endl;
  469. }
  470. catch (ProjectExporter::SaveError& error)
  471. {
  472. owner.addError (error.message);
  473. }
  474. return jobHasFinished;
  475. }
  476. private:
  477. ProjectSaver& owner;
  478. ScopedPointer<ProjectExporter> exporter;
  479. const OwnedArray<LibraryModule>& modules;
  480. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterJob)
  481. };
  482. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver)
  483. };
  484. #endif // __JUCER_PROJECTSAVER_JUCEHEADER__