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.

832 lines
30KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #pragma once
  14. #include "jucer_ResourceFile.h"
  15. #include "../Project/jucer_Module.h"
  16. #include "jucer_ProjectExporter.h"
  17. //==============================================================================
  18. class ProjectSaver
  19. {
  20. public:
  21. ProjectSaver (Project& p, const File& file)
  22. : project (p),
  23. projectFile (file),
  24. generatedCodeFolder (project.getGeneratedCodeFolder()),
  25. generatedFilesGroup (Project::Item::createGroup (project, getJuceCodeGroupName(), "__generatedcode__", true))
  26. {
  27. generatedFilesGroup.setID (getGeneratedGroupID());
  28. }
  29. struct SaveThread : public ThreadWithProgressWindow
  30. {
  31. public:
  32. SaveThread (ProjectSaver& ps, bool wait, const String& exp)
  33. : ThreadWithProgressWindow ("Saving...", true, false),
  34. saver (ps),
  35. shouldWaitAfterSaving (wait),
  36. specifiedExporterToSave (exp)
  37. {}
  38. void run() override
  39. {
  40. setProgress (-1);
  41. result = saver.save (false, shouldWaitAfterSaving, specifiedExporterToSave);
  42. }
  43. ProjectSaver& saver;
  44. Result result = Result::ok();
  45. bool shouldWaitAfterSaving;
  46. String specifiedExporterToSave;
  47. JUCE_DECLARE_NON_COPYABLE (SaveThread)
  48. };
  49. Result save (bool showProgressBox, bool waitAfterSaving, const String& specifiedExporterToSave)
  50. {
  51. if (showProgressBox)
  52. {
  53. SaveThread thread (*this, waitAfterSaving, specifiedExporterToSave);
  54. thread.runThread();
  55. return thread.result;
  56. }
  57. projectLineFeed = project.getProjectLineFeed();
  58. auto appConfigUserContent = loadUserContentFromAppConfig();
  59. auto oldFile = project.getFile();
  60. project.setFile (projectFile);
  61. OwnedArray<LibraryModule> modules;
  62. project.getEnabledModules().createRequiredModules (modules);
  63. checkModuleValidity (modules);
  64. if (errors.size() == 0)
  65. {
  66. writeMainProjectFile();
  67. project.updateModificationTime();
  68. auto projectRootHash = project.getProjectRoot().toXmlString().hashCode();
  69. if (project.isAudioPluginProject())
  70. {
  71. if (project.shouldBuildUnityPlugin())
  72. writeUnityScriptFile();
  73. }
  74. saveBasicProjectItems (modules, appConfigUserContent);
  75. writeProjects (modules, specifiedExporterToSave, ! showProgressBox);
  76. runPostExportScript();
  77. // if the project root has changed after writing the other files then re-save it
  78. if (project.getProjectRoot().toXmlString().hashCode() != projectRootHash)
  79. {
  80. writeMainProjectFile();
  81. project.updateModificationTime();
  82. }
  83. if (generatedCodeFolder.exists())
  84. {
  85. writeReadmeFile();
  86. deleteUnwantedFilesIn (generatedCodeFolder);
  87. }
  88. if (errors.size() == 0)
  89. {
  90. // Workaround for a bug where Xcode thinks the project is invalid if opened immediately
  91. // after writing
  92. if (waitAfterSaving)
  93. Thread::sleep (2000);
  94. return Result::ok();
  95. }
  96. }
  97. project.setFile (oldFile);
  98. return Result::fail (errors[0]);
  99. }
  100. Result saveResourcesOnly()
  101. {
  102. writeBinaryDataFiles();
  103. if (errors.size() > 0)
  104. return Result::fail (errors[0]);
  105. return Result::ok();
  106. }
  107. void saveBasicProjectItems (const OwnedArray<LibraryModule>& modules, const String& appConfigUserContent)
  108. {
  109. writePluginDefines();
  110. writeAppConfigFile (modules, appConfigUserContent);
  111. writeBinaryDataFiles();
  112. writeAppHeader (modules);
  113. writeModuleCppWrappers (modules);
  114. }
  115. Result saveContentNeededForLiveBuild()
  116. {
  117. OwnedArray<LibraryModule> modules;
  118. project.getEnabledModules().createRequiredModules (modules);
  119. checkModuleValidity (modules);
  120. if (errors.size() == 0)
  121. {
  122. saveBasicProjectItems (modules, loadUserContentFromAppConfig());
  123. return Result::ok();
  124. }
  125. return Result::fail (errors[0]);
  126. }
  127. Project::Item saveGeneratedFile (const String& filePath, const MemoryOutputStream& newData)
  128. {
  129. if (! generatedCodeFolder.createDirectory())
  130. {
  131. addError ("Couldn't create folder: " + generatedCodeFolder.getFullPathName());
  132. return Project::Item (project, ValueTree(), false);
  133. }
  134. auto file = generatedCodeFolder.getChildFile (filePath);
  135. if (replaceFileIfDifferent (file, newData))
  136. return addFileToGeneratedGroup (file);
  137. return { project, {}, true };
  138. }
  139. Project::Item addFileToGeneratedGroup (const File& file)
  140. {
  141. auto item = generatedFilesGroup.findItemForFile (file);
  142. if (item.isValid())
  143. return item;
  144. generatedFilesGroup.addFileAtIndex (file, -1, true);
  145. return generatedFilesGroup.findItemForFile (file);
  146. }
  147. static void writeAutoGenWarningComment (OutputStream& out)
  148. {
  149. out << "/*" << newLine << newLine
  150. << " IMPORTANT! This file is auto-generated each time you save your" << newLine
  151. << " project - if you alter its contents, your changes may be overwritten!" << newLine
  152. << newLine;
  153. }
  154. static const char* getGeneratedGroupID() noexcept { return "__jucelibfiles"; }
  155. Project::Item& getGeneratedCodeGroup() { return generatedFilesGroup; }
  156. static String getJuceCodeGroupName() { return "JUCE Library Code"; }
  157. File getGeneratedCodeFolder() const { return generatedCodeFolder; }
  158. bool replaceFileIfDifferent (const File& f, const MemoryOutputStream& newData)
  159. {
  160. filesCreated.add (f);
  161. if (!build_tools::overwriteFileWithNewDataIfDifferent (f, newData))
  162. {
  163. addError ("Can't write to file: " + f.getFullPathName());
  164. return false;
  165. }
  166. return true;
  167. }
  168. static bool shouldFolderBeIgnoredWhenCopying (const File& f)
  169. {
  170. return f.getFileName() == ".git" || f.getFileName() == ".svn" || f.getFileName() == ".cvs";
  171. }
  172. bool copyFolder (const File& source, const File& dest)
  173. {
  174. if (source.isDirectory() && dest.createDirectory())
  175. {
  176. for (auto& f : source.findChildFiles (File::findFiles, false))
  177. {
  178. auto target = dest.getChildFile (f.getFileName());
  179. filesCreated.add (target);
  180. if (! f.copyFileTo (target))
  181. return false;
  182. }
  183. for (auto& f : source.findChildFiles (File::findDirectories, false))
  184. if (! shouldFolderBeIgnoredWhenCopying (f))
  185. if (! copyFolder (f, dest.getChildFile (f.getFileName())))
  186. return false;
  187. return true;
  188. }
  189. return false;
  190. }
  191. Project& project;
  192. SortedSet<File> filesCreated;
  193. private:
  194. const File projectFile, generatedCodeFolder;
  195. Project::Item generatedFilesGroup;
  196. StringArray errors;
  197. CriticalSection errorLock;
  198. bool hasBinaryData = false;
  199. String projectLineFeed = "\r\n";
  200. // Recursively clears out any files in a folder that we didn't create, but avoids
  201. // any folders containing hidden files that might be used by version-control systems.
  202. bool deleteUnwantedFilesIn (const File& parent)
  203. {
  204. bool folderIsNowEmpty = true;
  205. Array<File> filesToDelete;
  206. for (const auto& i : RangedDirectoryIterator (parent, false, "*", File::findFilesAndDirectories))
  207. {
  208. auto f = i.getFile();
  209. if (filesCreated.contains (f) || shouldFileBeKept (f.getFileName()))
  210. {
  211. folderIsNowEmpty = false;
  212. }
  213. else if (i.isDirectory())
  214. {
  215. if (deleteUnwantedFilesIn (f))
  216. filesToDelete.add (f);
  217. else
  218. folderIsNowEmpty = false;
  219. }
  220. else
  221. {
  222. filesToDelete.add (f);
  223. }
  224. }
  225. for (int j = filesToDelete.size(); --j >= 0;)
  226. filesToDelete.getReference(j).deleteRecursively();
  227. return folderIsNowEmpty;
  228. }
  229. static bool shouldFileBeKept (const String& filename)
  230. {
  231. static const char* filesToKeep[] = { ".svn", ".cvs", "CMakeLists.txt" };
  232. for (auto* f : filesToKeep)
  233. if (filename == f)
  234. return true;
  235. return false;
  236. }
  237. void writeMainProjectFile()
  238. {
  239. if (auto xml = project.getProjectRoot().createXml())
  240. {
  241. XmlElement::TextFormat format;
  242. format.newLineChars = projectLineFeed.toRawUTF8();
  243. MemoryOutputStream mo (8192);
  244. xml->writeTo (mo, format);
  245. replaceFileIfDifferent (projectFile, mo);
  246. }
  247. else
  248. {
  249. jassertfalse;
  250. }
  251. }
  252. static int findLongestModuleName (const OwnedArray<LibraryModule>& modules)
  253. {
  254. int longest = 0;
  255. for (auto& m : modules)
  256. longest = jmax (longest, m->getID().length());
  257. return longest;
  258. }
  259. File getAppConfigFile() const { return generatedCodeFolder.getChildFile (Project::getAppConfigFilename()); }
  260. File getPluginDefinesFile() const { return generatedCodeFolder.getChildFile (Project::getPluginDefinesFilename()); }
  261. String loadUserContentFromAppConfig() const
  262. {
  263. StringArray userContent;
  264. bool foundCodeSection = false;
  265. auto lines = StringArray::fromLines (getAppConfigFile().loadFileAsString());
  266. for (int i = 0; i < lines.size(); ++i)
  267. {
  268. if (lines[i].contains ("[BEGIN_USER_CODE_SECTION]"))
  269. {
  270. for (int j = i + 1; j < lines.size() && ! lines[j].contains ("[END_USER_CODE_SECTION]"); ++j)
  271. userContent.add (lines[j]);
  272. foundCodeSection = true;
  273. break;
  274. }
  275. }
  276. if (! foundCodeSection)
  277. {
  278. userContent.add ({});
  279. userContent.add ("// (You can add your own code in this section, and the Projucer will not overwrite it)");
  280. userContent.add ({});
  281. }
  282. return userContent.joinIntoString (projectLineFeed) + projectLineFeed;
  283. }
  284. void checkModuleValidity (OwnedArray<LibraryModule>& modules)
  285. {
  286. if (project.getNumExporters() == 0)
  287. {
  288. addError ("No exporters found!\n"
  289. "Please add an exporter before saving.");
  290. return;
  291. }
  292. for (auto moduleIter = modules.begin(); moduleIter != modules.end(); ++moduleIter)
  293. {
  294. if (auto* module = *moduleIter)
  295. {
  296. if (! module->isValid())
  297. {
  298. addError ("At least one of your JUCE module paths is invalid!\n"
  299. "Please go to the Modules settings page and ensure each path points to the correct JUCE modules folder.");
  300. return;
  301. }
  302. if (project.getEnabledModules().getExtraDependenciesNeeded (module->getID()).size() > 0)
  303. {
  304. addError ("At least one of your modules has missing dependencies!\n"
  305. "Please go to the settings page of the highlighted modules and add the required dependencies.");
  306. return;
  307. }
  308. }
  309. else
  310. {
  311. // this should never happen!
  312. jassertfalse;
  313. }
  314. }
  315. }
  316. void writePluginDefines (MemoryOutputStream& out) const
  317. {
  318. const auto pluginDefines = getAudioPluginDefines();
  319. if (pluginDefines.isEmpty())
  320. return;
  321. writeAutoGenWarningComment (out);
  322. out << "*/" << newLine << newLine
  323. << "#pragma once" << newLine << newLine
  324. << pluginDefines << newLine;
  325. }
  326. void writeAppConfig (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules, const String& userContent)
  327. {
  328. if (! project.shouldUseAppConfig())
  329. return;
  330. writeAutoGenWarningComment (out);
  331. out << " There's a section below where you can add your own custom code safely, and the" << newLine
  332. << " Projucer will preserve the contents of that block, but the best way to change" << newLine
  333. << " any of these definitions is by using the Projucer's project settings." << newLine
  334. << newLine
  335. << " Any commented-out settings will assume their default values." << newLine
  336. << newLine
  337. << "*/" << newLine
  338. << newLine;
  339. out << "#pragma once" << newLine
  340. << newLine
  341. << "//==============================================================================" << newLine
  342. << "// [BEGIN_USER_CODE_SECTION]" << newLine
  343. << userContent
  344. << "// [END_USER_CODE_SECTION]" << newLine;
  345. if (getPluginDefinesFile().existsAsFile() && getAudioPluginDefines().isNotEmpty())
  346. out << newLine << CodeHelpers::createIncludeStatement (Project::getPluginDefinesFilename()) << newLine;
  347. out << newLine
  348. << "/*" << newLine
  349. << " ==============================================================================" << newLine
  350. << newLine
  351. << " In accordance with the terms of the JUCE 5 End-Use License Agreement, the" << newLine
  352. << " JUCE Code in SECTION A cannot be removed, changed or otherwise rendered" << newLine
  353. << " ineffective unless you have a JUCE Indie or Pro license, or are using JUCE" << newLine
  354. << " under the GPL v3 license." << newLine
  355. << newLine
  356. << " End User License Agreement: www.juce.com/juce-5-licence" << newLine
  357. << newLine
  358. << " ==============================================================================" << newLine
  359. << "*/" << newLine
  360. << newLine
  361. << "// BEGIN SECTION A" << newLine
  362. << newLine
  363. << "#ifndef JUCE_DISPLAY_SPLASH_SCREEN" << newLine
  364. << " #define JUCE_DISPLAY_SPLASH_SCREEN " << (project.shouldDisplaySplashScreen() ? "1" : "0") << newLine
  365. << "#endif" << newLine << newLine
  366. << "#ifndef JUCE_REPORT_APP_USAGE" << newLine
  367. << " #define JUCE_REPORT_APP_USAGE " << (project.shouldReportAppUsage() ? "1" : "0") << newLine
  368. << "#endif" << newLine
  369. << newLine
  370. << "// END SECTION A" << newLine
  371. << newLine
  372. << "#define JUCE_USE_DARK_SPLASH_SCREEN " << (project.getSplashScreenColourString() == "Dark" ? "1" : "0") << newLine
  373. << newLine
  374. << "#define JUCE_PROJUCER_VERSION 0x" << String::toHexString (ProjectInfo::versionNumber) << newLine;
  375. out << newLine
  376. << "//==============================================================================" << newLine;
  377. auto longestName = findLongestModuleName (modules);
  378. for (auto& m : modules)
  379. out << "#define JUCE_MODULE_AVAILABLE_" << m->getID()
  380. << String::repeatedString (" ", longestName + 5 - m->getID().length()) << " 1" << newLine;
  381. out << newLine << "#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1" << newLine;
  382. for (auto& m : modules)
  383. {
  384. OwnedArray<Project::ConfigFlag> flags;
  385. m->getConfigFlags (project, flags);
  386. if (flags.size() > 0)
  387. {
  388. out << newLine
  389. << "//==============================================================================" << newLine
  390. << "// " << m->getID() << " flags:" << newLine;
  391. for (auto* flag : flags)
  392. {
  393. out << newLine
  394. << "#ifndef " << flag->symbol
  395. << newLine
  396. << (flag->value.isUsingDefault() ? " //#define " : " #define ") << flag->symbol << " " << (flag->value.get() ? "1" : "0")
  397. << newLine
  398. << "#endif"
  399. << newLine;
  400. }
  401. }
  402. }
  403. {
  404. auto& type = project.getProjectType();
  405. auto isStandaloneApplication = (! type.isAudioPlugin() && ! type.isDynamicLibrary());
  406. out << newLine
  407. << "//==============================================================================" << newLine
  408. << "#ifndef JUCE_STANDALONE_APPLICATION" << newLine
  409. << " #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)" << newLine
  410. << " #define JUCE_STANDALONE_APPLICATION JucePlugin_Build_Standalone" << newLine
  411. << " #else" << newLine
  412. << " #define JUCE_STANDALONE_APPLICATION " << (isStandaloneApplication ? "1" : "0") << newLine
  413. << " #endif" << newLine
  414. << "#endif" << newLine;
  415. }
  416. }
  417. template <typename WriterCallback>
  418. void writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback)
  419. {
  420. MemoryOutputStream mem;
  421. mem.setNewLineString (projectLineFeed);
  422. writerCallback (mem);
  423. if (mem.getDataSize() != 0)
  424. {
  425. saveGeneratedFile (name, mem);
  426. return;
  427. }
  428. const auto destFile = generatedCodeFolder.getChildFile (name);
  429. if (destFile.existsAsFile())
  430. {
  431. if (! destFile.deleteFile())
  432. addError ("Couldn't remove unnecessary file: " + destFile.getFullPathName());
  433. }
  434. }
  435. void writePluginDefines()
  436. {
  437. writeOrRemoveGeneratedFile (Project::getPluginDefinesFilename(), [&] (MemoryOutputStream& mem)
  438. {
  439. writePluginDefines (mem);
  440. });
  441. }
  442. void writeAppConfigFile (const OwnedArray<LibraryModule>& modules, const String& userContent)
  443. {
  444. writeOrRemoveGeneratedFile (Project::getAppConfigFilename(), [&] (MemoryOutputStream& mem)
  445. {
  446. writeAppConfig (mem, modules, userContent);
  447. });
  448. }
  449. void writeAppHeader (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules)
  450. {
  451. writeAutoGenWarningComment (out);
  452. out << " This is the header file that your files should include in order to get all the" << newLine
  453. << " JUCE library headers. You should avoid including the JUCE headers directly in" << newLine
  454. << " your own source files, because that wouldn't pick up the correct configuration" << newLine
  455. << " options for your app." << newLine
  456. << newLine
  457. << "*/" << newLine << newLine;
  458. out << "#pragma once" << newLine << newLine;
  459. if (getAppConfigFile().exists() && project.shouldUseAppConfig())
  460. out << CodeHelpers::createIncludeStatement (Project::getAppConfigFilename()) << newLine;
  461. if (modules.size() > 0)
  462. {
  463. out << newLine;
  464. for (int i = 0; i < modules.size(); ++i)
  465. modules.getUnchecked(i)->writeIncludes (*this, out);
  466. out << newLine;
  467. }
  468. if (hasBinaryData && project.shouldIncludeBinaryInJuceHeader())
  469. out << CodeHelpers::createIncludeStatement (project.getBinaryDataHeaderFile(), getAppConfigFile()) << newLine;
  470. out << newLine
  471. << "#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION" << newLine
  472. << " /** If you've hit this error then the version of the Projucer that was used to generate this project is" << newLine
  473. << " older than the version of the JUCE modules being included. To fix this error, re-save your project" << newLine
  474. << " using the latest version of the Projucer or, if you aren't using the Projucer to manage your project," << newLine
  475. << " remove the JUCE_PROJUCER_VERSION define from the AppConfig.h file." << newLine
  476. << " */" << newLine
  477. << " #error \"This project was last saved using an outdated version of the Projucer! Re-save this project with the latest version to fix this error.\"" << newLine
  478. << "#endif" << newLine
  479. << newLine;
  480. if (project.shouldAddUsingNamespaceToJuceHeader())
  481. out << "#if ! DONT_SET_USING_JUCE_NAMESPACE" << newLine
  482. << " // If your code uses a lot of JUCE classes, then this will obviously save you" << newLine
  483. << " // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE." << newLine
  484. << " using namespace juce;" << newLine
  485. << "#endif" << newLine
  486. << newLine;
  487. out << "#if ! JUCE_DONT_DECLARE_PROJECTINFO" << newLine
  488. << "namespace ProjectInfo" << newLine
  489. << "{" << newLine
  490. << " const char* const projectName = " << CppTokeniserFunctions::addEscapeChars (project.getProjectNameString()).quoted() << ";" << newLine
  491. << " const char* const companyName = " << CppTokeniserFunctions::addEscapeChars (project.getCompanyNameString()).quoted() << ";" << newLine
  492. << " const char* const versionString = " << CppTokeniserFunctions::addEscapeChars (project.getVersionString()).quoted() << ";" << newLine
  493. << " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine
  494. << "}" << newLine
  495. << "#endif" << newLine;
  496. }
  497. void writeAppHeader (const OwnedArray<LibraryModule>& modules)
  498. {
  499. MemoryOutputStream mem;
  500. mem.setNewLineString (projectLineFeed);
  501. writeAppHeader (mem, modules);
  502. saveGeneratedFile (Project::getJuceSourceHFilename(), mem);
  503. }
  504. void writeModuleCppWrappers (const OwnedArray<LibraryModule>& modules)
  505. {
  506. for (auto* module : modules)
  507. {
  508. for (auto& cu : module->getAllCompileUnits())
  509. {
  510. MemoryOutputStream mem;
  511. mem.setNewLineString (projectLineFeed);
  512. writeAutoGenWarningComment (mem);
  513. mem << "*/" << newLine << newLine;
  514. if (project.shouldUseAppConfig())
  515. mem << "#include " << Project::getAppConfigFilename().quoted() << newLine;
  516. mem << "#include <";
  517. if (cu.file.getFileExtension() != ".r") // .r files are included without the path
  518. mem << module->getID() << "/";
  519. mem << cu.file.getFileName() << ">" << newLine;
  520. replaceFileIfDifferent (generatedCodeFolder.getChildFile (cu.getFilenameForProxyFile()), mem);
  521. }
  522. }
  523. }
  524. void writeBinaryDataFiles()
  525. {
  526. auto binaryDataH = project.getBinaryDataHeaderFile();
  527. JucerResourceFile resourceFile (project);
  528. if (resourceFile.getNumFiles() > 0)
  529. {
  530. auto dataNamespace = project.getBinaryDataNamespaceString().trim();
  531. if (dataNamespace.isEmpty())
  532. dataNamespace = "BinaryData";
  533. resourceFile.setClassName (dataNamespace);
  534. auto maxSize = project.getMaxBinaryFileSize();
  535. if (maxSize <= 0)
  536. maxSize = 10 * 1024 * 1024;
  537. auto r = resourceFile.write (maxSize);
  538. if (r.result.wasOk())
  539. {
  540. hasBinaryData = true;
  541. for (auto& f : r.filesCreated)
  542. {
  543. filesCreated.add (f);
  544. generatedFilesGroup.addFileRetainingSortOrder (f, ! f.hasFileExtension (".h"));
  545. }
  546. }
  547. else
  548. {
  549. addError (r.result.getErrorMessage());
  550. }
  551. }
  552. else
  553. {
  554. for (int i = 20; --i >= 0;)
  555. project.getBinaryDataCppFile (i).deleteFile();
  556. binaryDataH.deleteFile();
  557. }
  558. }
  559. void writeReadmeFile()
  560. {
  561. MemoryOutputStream out;
  562. out.setNewLineString (projectLineFeed);
  563. out << newLine
  564. << " Important Note!!" << newLine
  565. << " ================" << newLine
  566. << newLine
  567. << "The purpose of this folder is to contain files that are auto-generated by the Projucer," << newLine
  568. << "and ALL files in this folder will be mercilessly DELETED and completely re-written whenever" << newLine
  569. << "the Projucer saves your project." << newLine
  570. << newLine
  571. << "Therefore, it's a bad idea to make any manual changes to the files in here, or to" << newLine
  572. << "put any of your own files in here if you don't want to lose them. (Of course you may choose" << newLine
  573. << "to add the folder's contents to your version-control system so that you can re-merge your own" << newLine
  574. << "modifications after the Projucer has saved its changes)." << newLine;
  575. replaceFileIfDifferent (generatedCodeFolder.getChildFile ("ReadMe.txt"), out);
  576. }
  577. void addError (const String& message)
  578. {
  579. const ScopedLock sl (errorLock);
  580. errors.add (message);
  581. }
  582. String getAudioPluginDefines() const;
  583. void writeUnityScriptFile()
  584. {
  585. auto unityScriptContents = replaceLineFeeds (BinaryData::UnityPluginGUIScript_cs_in,
  586. projectLineFeed);
  587. auto projectName = Project::addUnityPluginPrefixIfNecessary (project.getProjectNameString());
  588. unityScriptContents = unityScriptContents.replace ("${plugin_class_name}", projectName.replace (" ", "_"))
  589. .replace ("${plugin_name}", projectName)
  590. .replace ("${plugin_vendor}", project.getPluginManufacturerString())
  591. .replace ("${plugin_description}", project.getPluginDescriptionString());
  592. auto f = getGeneratedCodeFolder().getChildFile (project.getUnityScriptName());
  593. MemoryOutputStream out;
  594. out << unityScriptContents;
  595. replaceFileIfDifferent (f, out);
  596. }
  597. void writeProjects (const OwnedArray<LibraryModule>&, const String&, bool);
  598. void runPostExportScript()
  599. {
  600. #if JUCE_WINDOWS
  601. auto cmdString = project.getPostExportShellCommandWinString();
  602. #else
  603. auto cmdString = project.getPostExportShellCommandPosixString();
  604. #endif
  605. auto shellCommand = cmdString.replace ("%%1%%", project.getProjectFolder().getFullPathName());
  606. if (shellCommand.isNotEmpty())
  607. {
  608. #if JUCE_WINDOWS
  609. StringArray argList ("cmd.exe", "/c");
  610. #else
  611. StringArray argList ("/bin/sh", "-c");
  612. #endif
  613. argList.add (shellCommand);
  614. ChildProcess shellProcess;
  615. if (! shellProcess.start (argList))
  616. {
  617. addError ("Failed to run shell command: " + argList.joinIntoString (" "));
  618. return;
  619. }
  620. if (! shellProcess.waitForProcessToFinish (10000))
  621. {
  622. addError ("Timeout running shell command: " + argList.joinIntoString (" "));
  623. return;
  624. }
  625. auto exitCode = shellProcess.getExitCode();
  626. if (exitCode != 0)
  627. addError ("Shell command: " + argList.joinIntoString (" ") + " failed with exit code: " + String (exitCode));
  628. }
  629. }
  630. void saveExporter (ProjectExporter* exporter, const OwnedArray<LibraryModule>& modules)
  631. {
  632. try
  633. {
  634. exporter->create (modules);
  635. if (! exporter->isCLion())
  636. std::cout << "Finished saving: " << exporter->getName() << std::endl;
  637. }
  638. catch (build_tools::SaveError& error)
  639. {
  640. addError (error.message);
  641. }
  642. }
  643. class ExporterJob : public ThreadPoolJob
  644. {
  645. public:
  646. ExporterJob (ProjectSaver& ps, ProjectExporter* pe,
  647. const OwnedArray<LibraryModule>& moduleList)
  648. : ThreadPoolJob ("export"),
  649. owner (ps), exporter (pe), modules (moduleList)
  650. {
  651. }
  652. JobStatus runJob() override
  653. {
  654. owner.saveExporter (exporter.get(), modules);
  655. return jobHasFinished;
  656. }
  657. private:
  658. ProjectSaver& owner;
  659. std::unique_ptr<ProjectExporter> exporter;
  660. const OwnedArray<LibraryModule>& modules;
  661. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterJob)
  662. };
  663. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver)
  664. };