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.

818 lines
28KB

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