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.

827 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. << "// END SECTION A" << newLine
  367. << newLine
  368. << "#define JUCE_USE_DARK_SPLASH_SCREEN " << (project.getSplashScreenColourString() == "Dark" ? "1" : "0") << newLine
  369. << newLine
  370. << "#define JUCE_PROJUCER_VERSION 0x" << String::toHexString (ProjectInfo::versionNumber) << newLine;
  371. out << newLine
  372. << "//==============================================================================" << newLine;
  373. auto longestName = findLongestModuleName (modules);
  374. for (auto& m : modules)
  375. out << "#define JUCE_MODULE_AVAILABLE_" << m->getID()
  376. << String::repeatedString (" ", longestName + 5 - m->getID().length()) << " 1" << newLine;
  377. out << newLine << "#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1" << newLine;
  378. for (auto& m : modules)
  379. {
  380. OwnedArray<Project::ConfigFlag> flags;
  381. m->getConfigFlags (project, flags);
  382. if (flags.size() > 0)
  383. {
  384. out << newLine
  385. << "//==============================================================================" << newLine
  386. << "// " << m->getID() << " flags:" << newLine;
  387. for (auto* flag : flags)
  388. {
  389. out << newLine
  390. << "#ifndef " << flag->symbol
  391. << newLine
  392. << (flag->value.isUsingDefault() ? " //#define " : " #define ") << flag->symbol << " " << (flag->value.get() ? "1" : "0")
  393. << newLine
  394. << "#endif"
  395. << newLine;
  396. }
  397. }
  398. }
  399. {
  400. auto& type = project.getProjectType();
  401. auto isStandaloneApplication = (! type.isAudioPlugin() && ! type.isDynamicLibrary());
  402. out << newLine
  403. << "//==============================================================================" << newLine
  404. << "#ifndef JUCE_STANDALONE_APPLICATION" << newLine
  405. << " #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)" << newLine
  406. << " #define JUCE_STANDALONE_APPLICATION JucePlugin_Build_Standalone" << newLine
  407. << " #else" << newLine
  408. << " #define JUCE_STANDALONE_APPLICATION " << (isStandaloneApplication ? "1" : "0") << newLine
  409. << " #endif" << newLine
  410. << "#endif" << newLine;
  411. }
  412. }
  413. template <typename WriterCallback>
  414. void writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback)
  415. {
  416. MemoryOutputStream mem;
  417. mem.setNewLineString (projectLineFeed);
  418. writerCallback (mem);
  419. if (mem.getDataSize() != 0)
  420. {
  421. saveGeneratedFile (name, mem);
  422. return;
  423. }
  424. const auto destFile = generatedCodeFolder.getChildFile (name);
  425. if (destFile.existsAsFile())
  426. {
  427. if (! destFile.deleteFile())
  428. addError ("Couldn't remove unnecessary file: " + destFile.getFullPathName());
  429. }
  430. }
  431. void writePluginDefines()
  432. {
  433. writeOrRemoveGeneratedFile (Project::getPluginDefinesFilename(), [&] (MemoryOutputStream& mem)
  434. {
  435. writePluginDefines (mem);
  436. });
  437. }
  438. void writeAppConfigFile (const OwnedArray<LibraryModule>& modules, const String& userContent)
  439. {
  440. writeOrRemoveGeneratedFile (Project::getAppConfigFilename(), [&] (MemoryOutputStream& mem)
  441. {
  442. writeAppConfig (mem, modules, userContent);
  443. });
  444. }
  445. void writeAppHeader (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules)
  446. {
  447. writeAutoGenWarningComment (out);
  448. out << " This is the header file that your files should include in order to get all the" << newLine
  449. << " JUCE library headers. You should avoid including the JUCE headers directly in" << newLine
  450. << " your own source files, because that wouldn't pick up the correct configuration" << newLine
  451. << " options for your app." << newLine
  452. << newLine
  453. << "*/" << newLine << newLine;
  454. out << "#pragma once" << newLine << newLine;
  455. if (getAppConfigFile().exists() && project.shouldUseAppConfig())
  456. out << CodeHelpers::createIncludeStatement (Project::getAppConfigFilename()) << newLine;
  457. if (modules.size() > 0)
  458. {
  459. out << newLine;
  460. for (int i = 0; i < modules.size(); ++i)
  461. modules.getUnchecked(i)->writeIncludes (*this, out);
  462. out << newLine;
  463. }
  464. if (hasBinaryData && project.shouldIncludeBinaryInJuceHeader())
  465. out << CodeHelpers::createIncludeStatement (project.getBinaryDataHeaderFile(), getAppConfigFile()) << newLine;
  466. out << newLine
  467. << "#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION" << newLine
  468. << " /** If you've hit this error then the version of the Projucer that was used to generate this project is" << newLine
  469. << " older than the version of the JUCE modules being included. To fix this error, re-save your project" << newLine
  470. << " using the latest version of the Projucer or, if you aren't using the Projucer to manage your project," << newLine
  471. << " remove the JUCE_PROJUCER_VERSION define from the AppConfig.h file." << newLine
  472. << " */" << newLine
  473. << " #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
  474. << "#endif" << newLine
  475. << newLine;
  476. if (project.shouldAddUsingNamespaceToJuceHeader())
  477. out << "#if ! DONT_SET_USING_JUCE_NAMESPACE" << newLine
  478. << " // If your code uses a lot of JUCE classes, then this will obviously save you" << newLine
  479. << " // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE." << newLine
  480. << " using namespace juce;" << newLine
  481. << "#endif" << newLine
  482. << newLine;
  483. out << "#if ! JUCE_DONT_DECLARE_PROJECTINFO" << newLine
  484. << "namespace ProjectInfo" << newLine
  485. << "{" << newLine
  486. << " const char* const projectName = " << CppTokeniserFunctions::addEscapeChars (project.getProjectNameString()).quoted() << ";" << newLine
  487. << " const char* const companyName = " << CppTokeniserFunctions::addEscapeChars (project.getCompanyNameString()).quoted() << ";" << newLine
  488. << " const char* const versionString = " << CppTokeniserFunctions::addEscapeChars (project.getVersionString()).quoted() << ";" << newLine
  489. << " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine
  490. << "}" << newLine
  491. << "#endif" << newLine;
  492. }
  493. void writeAppHeader (const OwnedArray<LibraryModule>& modules)
  494. {
  495. MemoryOutputStream mem;
  496. mem.setNewLineString (projectLineFeed);
  497. writeAppHeader (mem, modules);
  498. saveGeneratedFile (Project::getJuceSourceHFilename(), mem);
  499. }
  500. void writeModuleCppWrappers (const OwnedArray<LibraryModule>& modules)
  501. {
  502. for (auto* module : modules)
  503. {
  504. for (auto& cu : module->getAllCompileUnits())
  505. {
  506. MemoryOutputStream mem;
  507. mem.setNewLineString (projectLineFeed);
  508. writeAutoGenWarningComment (mem);
  509. mem << "*/" << newLine << newLine;
  510. if (project.shouldUseAppConfig())
  511. mem << "#include " << Project::getAppConfigFilename().quoted() << newLine;
  512. mem << "#include <";
  513. if (cu.file.getFileExtension() != ".r") // .r files are included without the path
  514. mem << module->getID() << "/";
  515. mem << cu.file.getFileName() << ">" << newLine;
  516. replaceFileIfDifferent (generatedCodeFolder.getChildFile (cu.getFilenameForProxyFile()), mem);
  517. }
  518. }
  519. }
  520. void writeBinaryDataFiles()
  521. {
  522. auto binaryDataH = project.getBinaryDataHeaderFile();
  523. JucerResourceFile resourceFile (project);
  524. if (resourceFile.getNumFiles() > 0)
  525. {
  526. auto dataNamespace = project.getBinaryDataNamespaceString().trim();
  527. if (dataNamespace.isEmpty())
  528. dataNamespace = "BinaryData";
  529. resourceFile.setClassName (dataNamespace);
  530. auto maxSize = project.getMaxBinaryFileSize();
  531. if (maxSize <= 0)
  532. maxSize = 10 * 1024 * 1024;
  533. auto r = resourceFile.write (maxSize);
  534. if (r.result.wasOk())
  535. {
  536. hasBinaryData = true;
  537. for (auto& f : r.filesCreated)
  538. {
  539. filesCreated.add (f);
  540. generatedFilesGroup.addFileRetainingSortOrder (f, ! f.hasFileExtension (".h"));
  541. }
  542. }
  543. else
  544. {
  545. addError (r.result.getErrorMessage());
  546. }
  547. }
  548. else
  549. {
  550. for (int i = 20; --i >= 0;)
  551. project.getBinaryDataCppFile (i).deleteFile();
  552. binaryDataH.deleteFile();
  553. }
  554. }
  555. void writeReadmeFile()
  556. {
  557. MemoryOutputStream out;
  558. out.setNewLineString (projectLineFeed);
  559. out << newLine
  560. << " Important Note!!" << newLine
  561. << " ================" << newLine
  562. << newLine
  563. << "The purpose of this folder is to contain files that are auto-generated by the Projucer," << newLine
  564. << "and ALL files in this folder will be mercilessly DELETED and completely re-written whenever" << newLine
  565. << "the Projucer saves your project." << newLine
  566. << newLine
  567. << "Therefore, it's a bad idea to make any manual changes to the files in here, or to" << newLine
  568. << "put any of your own files in here if you don't want to lose them. (Of course you may choose" << newLine
  569. << "to add the folder's contents to your version-control system so that you can re-merge your own" << newLine
  570. << "modifications after the Projucer has saved its changes)." << newLine;
  571. replaceFileIfDifferent (generatedCodeFolder.getChildFile ("ReadMe.txt"), out);
  572. }
  573. void addError (const String& message)
  574. {
  575. const ScopedLock sl (errorLock);
  576. errors.add (message);
  577. }
  578. String getAudioPluginDefines() const;
  579. void writeUnityScriptFile()
  580. {
  581. auto unityScriptContents = replaceLineFeeds (BinaryData::UnityPluginGUIScript_cs_in,
  582. projectLineFeed);
  583. auto projectName = Project::addUnityPluginPrefixIfNecessary (project.getProjectNameString());
  584. unityScriptContents = unityScriptContents.replace ("${plugin_class_name}", projectName.replace (" ", "_"))
  585. .replace ("${plugin_name}", projectName)
  586. .replace ("${plugin_vendor}", project.getPluginManufacturerString())
  587. .replace ("${plugin_description}", project.getPluginDescriptionString());
  588. auto f = getGeneratedCodeFolder().getChildFile (project.getUnityScriptName());
  589. MemoryOutputStream out;
  590. out << unityScriptContents;
  591. replaceFileIfDifferent (f, out);
  592. }
  593. void writeProjects (const OwnedArray<LibraryModule>&, const String&, bool);
  594. void runPostExportScript()
  595. {
  596. #if JUCE_WINDOWS
  597. auto cmdString = project.getPostExportShellCommandWinString();
  598. #else
  599. auto cmdString = project.getPostExportShellCommandPosixString();
  600. #endif
  601. auto shellCommand = cmdString.replace ("%%1%%", project.getProjectFolder().getFullPathName());
  602. if (shellCommand.isNotEmpty())
  603. {
  604. #if JUCE_WINDOWS
  605. StringArray argList ("cmd.exe", "/c");
  606. #else
  607. StringArray argList ("/bin/sh", "-c");
  608. #endif
  609. argList.add (shellCommand);
  610. ChildProcess shellProcess;
  611. if (! shellProcess.start (argList))
  612. {
  613. addError ("Failed to run shell command: " + argList.joinIntoString (" "));
  614. return;
  615. }
  616. if (! shellProcess.waitForProcessToFinish (10000))
  617. {
  618. addError ("Timeout running shell command: " + argList.joinIntoString (" "));
  619. return;
  620. }
  621. auto exitCode = shellProcess.getExitCode();
  622. if (exitCode != 0)
  623. addError ("Shell command: " + argList.joinIntoString (" ") + " failed with exit code: " + String (exitCode));
  624. }
  625. }
  626. void saveExporter (ProjectExporter* exporter, const OwnedArray<LibraryModule>& modules)
  627. {
  628. try
  629. {
  630. exporter->create (modules);
  631. if (! exporter->isCLion())
  632. std::cout << "Finished saving: " << exporter->getName() << std::endl;
  633. }
  634. catch (build_tools::SaveError& error)
  635. {
  636. addError (error.message);
  637. }
  638. }
  639. class ExporterJob : public ThreadPoolJob
  640. {
  641. public:
  642. ExporterJob (ProjectSaver& ps, ProjectExporter* pe,
  643. const OwnedArray<LibraryModule>& moduleList)
  644. : ThreadPoolJob ("export"),
  645. owner (ps), exporter (pe), modules (moduleList)
  646. {
  647. }
  648. JobStatus runJob() override
  649. {
  650. owner.saveExporter (exporter.get(), modules);
  651. return jobHasFinished;
  652. }
  653. private:
  654. ProjectSaver& owner;
  655. std::unique_ptr<ProjectExporter> exporter;
  656. const OwnedArray<LibraryModule>& modules;
  657. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ExporterJob)
  658. };
  659. JUCE_DECLARE_NON_COPYABLE_WITH_LEAK_DETECTOR (ProjectSaver)
  660. };