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.

842 lines
29KB

  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. #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.size() > 0)
  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.size() == 0)
  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.size() == 0)
  221. {
  222. writeMainProjectFile();
  223. project.updateModificationTime();
  224. auto projectRootHash = project.getProjectRoot().toXmlString().hashCode();
  225. if (project.isAudioPluginProject())
  226. {
  227. if (project.shouldBuildUnityPlugin())
  228. writeUnityScriptFile();
  229. }
  230. saveBasicProjectItems (modules, loadUserContentFromAppConfig());
  231. writeProjects (modules, specifiedExporterToSave);
  232. runPostExportScript();
  233. // if the project root has changed after writing the other files then re-save it
  234. if (project.getProjectRoot().toXmlString().hashCode() != projectRootHash)
  235. {
  236. writeMainProjectFile();
  237. project.updateModificationTime();
  238. }
  239. if (generatedCodeFolder.exists())
  240. {
  241. writeReadmeFile();
  242. deleteUnwantedFilesIn (generatedCodeFolder);
  243. }
  244. if (errors.size() == 0)
  245. return Result::ok();
  246. }
  247. project.setFile (oldProjectFile);
  248. return Result::fail (errors[0]);
  249. }
  250. //==============================================================================
  251. void ProjectSaver::writeMainProjectFile()
  252. {
  253. if (auto xml = project.getProjectRoot().createXml())
  254. {
  255. XmlElement::TextFormat format;
  256. format.newLineChars = projectLineFeed.toRawUTF8();
  257. MemoryOutputStream mo (8192);
  258. xml->writeTo (mo, format);
  259. replaceFileIfDifferent (project.getFile(), mo);
  260. }
  261. else
  262. {
  263. addError ("Failed to write main project file: " + project.getFile().getFullPathName());
  264. }
  265. }
  266. static void writeAutoGenWarningComment (OutputStream& out)
  267. {
  268. out << "/*" << newLine << newLine
  269. << " IMPORTANT! This file is auto-generated each time you save your" << newLine
  270. << " project - if you alter its contents, your changes may be overwritten!" << newLine
  271. << newLine;
  272. }
  273. void ProjectSaver::writePluginDefines (MemoryOutputStream& out) const
  274. {
  275. const auto pluginDefines = getAudioPluginDefines();
  276. if (pluginDefines.isEmpty())
  277. return;
  278. writeAutoGenWarningComment (out);
  279. out << "*/" << newLine << newLine
  280. << "#pragma once" << newLine << newLine
  281. << pluginDefines << newLine;
  282. }
  283. void ProjectSaver::writeAppConfig (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules, const String& userContent)
  284. {
  285. if (! project.shouldUseAppConfig())
  286. return;
  287. writeAutoGenWarningComment (out);
  288. out << " There's a section below where you can add your own custom code safely, and the" << newLine
  289. << " Projucer will preserve the contents of that block, but the best way to change" << newLine
  290. << " any of these definitions is by using the Projucer's project settings." << newLine
  291. << newLine
  292. << " Any commented-out settings will assume their default values." << newLine
  293. << newLine
  294. << "*/" << newLine
  295. << newLine;
  296. out << "#pragma once" << newLine
  297. << newLine
  298. << "//==============================================================================" << newLine
  299. << "// [BEGIN_USER_CODE_SECTION]" << newLine
  300. << userContent
  301. << "// [END_USER_CODE_SECTION]" << newLine;
  302. if (getPluginDefinesFile().existsAsFile() && getAudioPluginDefines().isNotEmpty())
  303. out << newLine << CodeHelpers::createIncludeStatement (Project::getPluginDefinesFilename()) << newLine;
  304. out << newLine
  305. << "/*" << newLine
  306. << " ==============================================================================" << newLine
  307. << newLine
  308. << " In accordance with the terms of the JUCE 5 End-Use License Agreement, the" << newLine
  309. << " JUCE Code in SECTION A cannot be removed, changed or otherwise rendered" << newLine
  310. << " ineffective unless you have a JUCE Indie or Pro license, or are using JUCE" << newLine
  311. << " under the GPL v3 license." << newLine
  312. << newLine
  313. << " End User License Agreement: www.juce.com/juce-5-licence" << newLine
  314. << newLine
  315. << " ==============================================================================" << newLine
  316. << "*/" << newLine
  317. << newLine
  318. << "// BEGIN SECTION A" << newLine
  319. << newLine
  320. << "#ifndef JUCE_DISPLAY_SPLASH_SCREEN" << newLine
  321. << " #define JUCE_DISPLAY_SPLASH_SCREEN " << (project.shouldDisplaySplashScreen() ? "1" : "0") << newLine
  322. << "#endif" << newLine << newLine
  323. << "// END SECTION A" << newLine
  324. << newLine
  325. << "#define JUCE_USE_DARK_SPLASH_SCREEN " << (project.getSplashScreenColourString() == "Dark" ? "1" : "0") << newLine
  326. << newLine
  327. << "#define JUCE_PROJUCER_VERSION 0x" << String::toHexString (ProjectInfo::versionNumber) << newLine;
  328. out << newLine
  329. << "//==============================================================================" << newLine;
  330. auto longestModuleName = [&modules]()
  331. {
  332. int longest = 0;
  333. for (auto* module : modules)
  334. longest = jmax (longest, module->getID().length());
  335. return longest;
  336. }();
  337. for (auto* module : modules)
  338. {
  339. out << "#define JUCE_MODULE_AVAILABLE_" << module->getID()
  340. << String::repeatedString (" ", longestModuleName + 5 - module->getID().length()) << " 1" << newLine;
  341. }
  342. out << newLine << "#define JUCE_GLOBAL_MODULE_SETTINGS_INCLUDED 1" << newLine;
  343. for (auto* module : modules)
  344. {
  345. OwnedArray<Project::ConfigFlag> flags;
  346. module->getConfigFlags (project, flags);
  347. if (flags.size() > 0)
  348. {
  349. out << newLine
  350. << "//==============================================================================" << newLine
  351. << "// " << module->getID() << " flags:" << newLine;
  352. for (auto* flag : flags)
  353. {
  354. out << newLine
  355. << "#ifndef " << flag->symbol
  356. << newLine
  357. << (flag->value.isUsingDefault() ? " //#define " : " #define ") << flag->symbol << " " << (flag->value.get() ? "1" : "0")
  358. << newLine
  359. << "#endif"
  360. << newLine;
  361. }
  362. }
  363. }
  364. auto& type = project.getProjectType();
  365. auto isStandaloneApplication = (! type.isAudioPlugin() && ! type.isDynamicLibrary());
  366. out << newLine
  367. << "//==============================================================================" << newLine
  368. << "#ifndef JUCE_STANDALONE_APPLICATION" << newLine
  369. << " #if defined(JucePlugin_Name) && defined(JucePlugin_Build_Standalone)" << newLine
  370. << " #define JUCE_STANDALONE_APPLICATION JucePlugin_Build_Standalone" << newLine
  371. << " #else" << newLine
  372. << " #define JUCE_STANDALONE_APPLICATION " << (isStandaloneApplication ? "1" : "0") << newLine
  373. << " #endif" << newLine
  374. << "#endif" << newLine;
  375. }
  376. template <typename WriterCallback>
  377. void ProjectSaver::writeOrRemoveGeneratedFile (const String& name, WriterCallback&& writerCallback)
  378. {
  379. MemoryOutputStream mem;
  380. mem.setNewLineString (projectLineFeed);
  381. writerCallback (mem);
  382. if (mem.getDataSize() != 0)
  383. {
  384. saveGeneratedFile (name, mem);
  385. return;
  386. }
  387. const auto destFile = generatedCodeFolder.getChildFile (name);
  388. if (destFile.existsAsFile())
  389. {
  390. if (! destFile.deleteFile())
  391. addError ("Couldn't remove unnecessary file: " + destFile.getFullPathName());
  392. }
  393. }
  394. void ProjectSaver::writePluginDefines()
  395. {
  396. writeOrRemoveGeneratedFile (Project::getPluginDefinesFilename(), [&] (MemoryOutputStream& mem)
  397. {
  398. writePluginDefines (mem);
  399. });
  400. }
  401. void ProjectSaver::writeAppConfigFile (const OwnedArray<LibraryModule>& modules, const String& userContent)
  402. {
  403. writeOrRemoveGeneratedFile (Project::getAppConfigFilename(), [&] (MemoryOutputStream& mem)
  404. {
  405. writeAppConfig (mem, modules, userContent);
  406. });
  407. }
  408. void ProjectSaver::writeAppHeader (MemoryOutputStream& out, const OwnedArray<LibraryModule>& modules)
  409. {
  410. writeAutoGenWarningComment (out);
  411. out << " This is the header file that your files should include in order to get all the" << newLine
  412. << " JUCE library headers. You should avoid including the JUCE headers directly in" << newLine
  413. << " your own source files, because that wouldn't pick up the correct configuration" << newLine
  414. << " options for your app." << newLine
  415. << newLine
  416. << "*/" << newLine << newLine;
  417. out << "#pragma once" << newLine << newLine;
  418. if (getAppConfigFile().exists() && project.shouldUseAppConfig())
  419. out << CodeHelpers::createIncludeStatement (Project::getAppConfigFilename()) << newLine;
  420. if (modules.size() > 0)
  421. {
  422. out << newLine;
  423. for (auto* module : modules)
  424. module->writeIncludes (*this, out);
  425. out << newLine;
  426. }
  427. if (hasBinaryData && project.shouldIncludeBinaryInJuceHeader())
  428. out << CodeHelpers::createIncludeStatement (project.getBinaryDataHeaderFile(), getAppConfigFile()) << newLine;
  429. out << newLine
  430. << "#if defined (JUCE_PROJUCER_VERSION) && JUCE_PROJUCER_VERSION < JUCE_VERSION" << newLine
  431. << " /** If you've hit this error then the version of the Projucer that was used to generate this project is" << newLine
  432. << " older than the version of the JUCE modules being included. To fix this error, re-save your project" << newLine
  433. << " using the latest version of the Projucer or, if you aren't using the Projucer to manage your project," << newLine
  434. << " remove the JUCE_PROJUCER_VERSION define from the AppConfig.h file." << newLine
  435. << " */" << newLine
  436. << " #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
  437. << "#endif" << newLine
  438. << newLine;
  439. if (project.shouldAddUsingNamespaceToJuceHeader())
  440. out << "#if ! DONT_SET_USING_JUCE_NAMESPACE" << newLine
  441. << " // If your code uses a lot of JUCE classes, then this will obviously save you" << newLine
  442. << " // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE." << newLine
  443. << " using namespace juce;" << newLine
  444. << "#endif" << newLine;
  445. out << newLine
  446. << "#if ! JUCE_DONT_DECLARE_PROJECTINFO" << newLine
  447. << "namespace ProjectInfo" << newLine
  448. << "{" << newLine
  449. << " const char* const projectName = " << CppTokeniserFunctions::addEscapeChars (project.getProjectNameString()).quoted() << ";" << newLine
  450. << " const char* const companyName = " << CppTokeniserFunctions::addEscapeChars (project.getCompanyNameString()).quoted() << ";" << newLine
  451. << " const char* const versionString = " << CppTokeniserFunctions::addEscapeChars (project.getVersionString()).quoted() << ";" << newLine
  452. << " const int versionNumber = " << project.getVersionAsHex() << ";" << newLine
  453. << "}" << newLine
  454. << "#endif" << newLine;
  455. }
  456. void ProjectSaver::writeAppHeader (const OwnedArray<LibraryModule>& modules)
  457. {
  458. MemoryOutputStream mem;
  459. mem.setNewLineString (projectLineFeed);
  460. writeAppHeader (mem, modules);
  461. saveGeneratedFile (Project::getJuceSourceHFilename(), mem);
  462. }
  463. void ProjectSaver::writeModuleCppWrappers (const OwnedArray<LibraryModule>& modules)
  464. {
  465. for (auto* module : modules)
  466. {
  467. for (auto& cu : module->getAllCompileUnits())
  468. {
  469. MemoryOutputStream mem;
  470. mem.setNewLineString (projectLineFeed);
  471. writeAutoGenWarningComment (mem);
  472. mem << "*/" << newLine << newLine;
  473. if (project.shouldUseAppConfig())
  474. mem << "#include " << Project::getAppConfigFilename().quoted() << newLine;
  475. mem << "#include <";
  476. if (cu.file.getFileExtension() != ".r") // .r files are included without the path
  477. mem << module->getID() << "/";
  478. mem << cu.file.getFileName() << ">" << newLine;
  479. replaceFileIfDifferent (generatedCodeFolder.getChildFile (cu.getFilenameForProxyFile()), mem);
  480. }
  481. }
  482. }
  483. void ProjectSaver::writeBinaryDataFiles()
  484. {
  485. auto binaryDataH = project.getBinaryDataHeaderFile();
  486. JucerResourceFile resourceFile (project);
  487. if (resourceFile.getNumFiles() > 0)
  488. {
  489. auto dataNamespace = project.getBinaryDataNamespaceString().trim();
  490. if (dataNamespace.isEmpty())
  491. dataNamespace = "BinaryData";
  492. resourceFile.setClassName (dataNamespace);
  493. auto maxSize = project.getMaxBinaryFileSize();
  494. if (maxSize <= 0)
  495. maxSize = 10 * 1024 * 1024;
  496. Array<File> binaryDataFiles;
  497. auto r = resourceFile.write (maxSize);
  498. if (r.result.wasOk())
  499. {
  500. hasBinaryData = true;
  501. for (auto& f : r.filesCreated)
  502. {
  503. filesCreated.add (f);
  504. generatedFilesGroup.addFileRetainingSortOrder (f, ! f.hasFileExtension (".h"));
  505. }
  506. }
  507. else
  508. {
  509. addError (r.result.getErrorMessage());
  510. }
  511. }
  512. else
  513. {
  514. for (int i = 20; --i >= 0;)
  515. project.getBinaryDataCppFile (i).deleteFile();
  516. binaryDataH.deleteFile();
  517. }
  518. }
  519. void ProjectSaver::writeReadmeFile()
  520. {
  521. MemoryOutputStream out;
  522. out.setNewLineString (projectLineFeed);
  523. out << newLine
  524. << " Important Note!!" << newLine
  525. << " ================" << newLine
  526. << newLine
  527. << "The purpose of this folder is to contain files that are auto-generated by the Projucer," << newLine
  528. << "and ALL files in this folder will be mercilessly DELETED and completely re-written whenever" << newLine
  529. << "the Projucer saves your project." << newLine
  530. << newLine
  531. << "Therefore, it's a bad idea to make any manual changes to the files in here, or to" << newLine
  532. << "put any of your own files in here if you don't want to lose them. (Of course you may choose" << newLine
  533. << "to add the folder's contents to your version-control system so that you can re-merge your own" << newLine
  534. << "modifications after the Projucer has saved its changes)." << newLine;
  535. replaceFileIfDifferent (generatedCodeFolder.getChildFile ("ReadMe.txt"), out);
  536. }
  537. String ProjectSaver::getAudioPluginDefines() const
  538. {
  539. const auto flags = project.getAudioPluginFlags();
  540. if (flags.size() == 0)
  541. return {};
  542. MemoryOutputStream mem;
  543. mem.setNewLineString (projectLineFeed);
  544. mem << "//==============================================================================" << newLine
  545. << "// Audio plugin settings.." << newLine
  546. << newLine;
  547. for (int i = 0; i < flags.size(); ++i)
  548. {
  549. mem << "#ifndef " << flags.getAllKeys()[i] << newLine
  550. << " #define " << flags.getAllKeys()[i].paddedRight (' ', 32) << " "
  551. << flags.getAllValues()[i] << newLine
  552. << "#endif" << newLine;
  553. }
  554. return mem.toString().trim();
  555. }
  556. void ProjectSaver::writeUnityScriptFile()
  557. {
  558. auto unityScriptContents = replaceLineFeeds (BinaryData::UnityPluginGUIScript_cs_in,
  559. projectLineFeed);
  560. auto projectName = Project::addUnityPluginPrefixIfNecessary (project.getProjectNameString());
  561. unityScriptContents = unityScriptContents.replace ("${plugin_class_name}", projectName.replace (" ", "_"))
  562. .replace ("${plugin_name}", projectName)
  563. .replace ("${plugin_vendor}", project.getPluginManufacturerString())
  564. .replace ("${plugin_description}", project.getPluginDescriptionString());
  565. auto f = generatedCodeFolder.getChildFile (project.getUnityScriptName());
  566. MemoryOutputStream out;
  567. out << unityScriptContents;
  568. replaceFileIfDifferent (f, out);
  569. }
  570. void ProjectSaver::writeProjects (const OwnedArray<LibraryModule>& modules, ProjectExporter* specifiedExporterToSave)
  571. {
  572. ThreadPool threadPool;
  573. // keep a copy of the basic generated files group, as each exporter may modify it.
  574. auto originalGeneratedGroup = generatedFilesGroup.state.createCopy();
  575. CLionProjectExporter* clionExporter = nullptr;
  576. std::vector<std::unique_ptr<ProjectExporter>> exporters;
  577. try
  578. {
  579. for (Project::ExporterIterator exp (project); exp.next();)
  580. {
  581. if (specifiedExporterToSave != nullptr && exp->getName() != specifiedExporterToSave->getName())
  582. continue;
  583. exporters.push_back (std::move (exp.exporter));
  584. }
  585. for (auto& exporter : exporters)
  586. {
  587. exporter->initialiseDependencyPathValues();
  588. if (exporter->getTargetFolder().createDirectory())
  589. {
  590. if (exporter->isCLion())
  591. {
  592. clionExporter = dynamic_cast<CLionProjectExporter*> (exporter.get());
  593. }
  594. else
  595. {
  596. exporter->copyMainGroupFromProject();
  597. exporter->settings = exporter->settings.createCopy();
  598. exporter->addToExtraSearchPaths (build_tools::RelativePath ("JuceLibraryCode", build_tools::RelativePath::projectFolder));
  599. generatedFilesGroup.state = originalGeneratedGroup.createCopy();
  600. exporter->addSettingsForProjectType (project.getProjectType());
  601. for (auto* module : modules)
  602. module->addSettingsForModuleToExporter (*exporter, *this);
  603. generatedFilesGroup.sortAlphabetically (true, true);
  604. exporter->getAllGroups().add (generatedFilesGroup);
  605. }
  606. if (ProjucerApplication::getApp().isRunningCommandLine)
  607. saveExporter (*exporter, modules);
  608. else
  609. threadPool.addJob (new ExporterJob (*this, *exporter, modules), true);
  610. }
  611. else
  612. {
  613. addError ("Can't create folder: " + exporter->getTargetFolder().getFullPathName());
  614. }
  615. }
  616. }
  617. catch (build_tools::SaveError& saveError)
  618. {
  619. addError (saveError.message);
  620. }
  621. while (threadPool.getNumJobs() > 0)
  622. Thread::sleep (10);
  623. if (clionExporter != nullptr)
  624. {
  625. for (auto& exporter : exporters)
  626. clionExporter->writeCMakeListsExporterSection (exporter.get());
  627. std::cout << "Finished saving: " << clionExporter->getName() << std::endl;
  628. }
  629. }
  630. void ProjectSaver::runPostExportScript()
  631. {
  632. #if JUCE_WINDOWS
  633. auto cmdString = project.getPostExportShellCommandWinString();
  634. #else
  635. auto cmdString = project.getPostExportShellCommandPosixString();
  636. #endif
  637. auto shellCommand = cmdString.replace ("%%1%%", project.getProjectFolder().getFullPathName());
  638. if (shellCommand.isNotEmpty())
  639. {
  640. #if JUCE_WINDOWS
  641. StringArray argList ("cmd.exe", "/c");
  642. #else
  643. StringArray argList ("/bin/sh", "-c");
  644. #endif
  645. argList.add (shellCommand);
  646. ChildProcess shellProcess;
  647. if (! shellProcess.start (argList))
  648. {
  649. addError ("Failed to run shell command: " + argList.joinIntoString (" "));
  650. return;
  651. }
  652. if (! shellProcess.waitForProcessToFinish (10000))
  653. {
  654. addError ("Timeout running shell command: " + argList.joinIntoString (" "));
  655. return;
  656. }
  657. auto exitCode = shellProcess.getExitCode();
  658. if (exitCode != 0)
  659. addError ("Shell command: " + argList.joinIntoString (" ") + " failed with exit code: " + String (exitCode));
  660. }
  661. }
  662. void ProjectSaver::saveExporter (ProjectExporter& exporter, const OwnedArray<LibraryModule>& modules)
  663. {
  664. try
  665. {
  666. exporter.create (modules);
  667. if (! exporter.isCLion())
  668. std::cout << "Finished saving: " << exporter.getName() << std::endl;
  669. }
  670. catch (build_tools::SaveError& error)
  671. {
  672. addError (error.message);
  673. }
  674. }