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.

870 lines
30KB

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