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.

843 lines
29KB

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