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.

893 lines
29KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2013 - Raw Material Software Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "jucer_Module.h"
  18. #include "jucer_ProjectType.h"
  19. #include "../Project Saving/jucer_ProjectExporter.h"
  20. #include "../Project Saving/jucer_ProjectSaver.h"
  21. #include "jucer_AudioPluginModule.h"
  22. ModuleDescription::ModuleDescription (const File& manifest)
  23. : moduleInfo (JSON::parse (manifest)), manifestFile (manifest)
  24. {
  25. if (moduleInfo.isVoid() && manifestFile.exists())
  26. {
  27. var json;
  28. Result r (JSON::parse (manifestFile.loadFileAsString(), json));
  29. if (r.failed() && manifestFile.loadFileAsString().isNotEmpty())
  30. {
  31. DBG (r.getErrorMessage());
  32. jassertfalse; // broken JSON in a module manifest.
  33. }
  34. }
  35. }
  36. //==============================================================================
  37. ModuleList::ModuleList()
  38. {
  39. }
  40. ModuleList::ModuleList (const ModuleList& other)
  41. {
  42. operator= (other);
  43. }
  44. ModuleList& ModuleList::operator= (const ModuleList& other)
  45. {
  46. modules.clear();
  47. modules.addCopiesOf (other.modules);
  48. return *this;
  49. }
  50. const ModuleDescription* ModuleList::getModuleWithID (const String& moduleID) const
  51. {
  52. for (int i = 0; i < modules.size(); ++i)
  53. {
  54. ModuleDescription* m = modules.getUnchecked(i);
  55. if (m->getID() == moduleID)
  56. return m;
  57. }
  58. return nullptr;
  59. }
  60. struct ModuleSorter
  61. {
  62. static int compareElements (const ModuleDescription* m1, const ModuleDescription* m2)
  63. {
  64. return m1->getID().compareIgnoreCase (m2->getID());
  65. }
  66. };
  67. void ModuleList::sort()
  68. {
  69. ModuleSorter sorter;
  70. modules.sort (sorter);
  71. }
  72. StringArray ModuleList::getIDs() const
  73. {
  74. StringArray results;
  75. for (int i = 0; i < modules.size(); ++i)
  76. results.add (modules.getUnchecked(i)->getID());
  77. results.sort (true);
  78. return results;
  79. }
  80. Result ModuleList::addAllModulesInFolder (const File& path)
  81. {
  82. const File moduleDef (path.getChildFile (ModuleDescription::getManifestFileName()));
  83. if (moduleDef.exists())
  84. {
  85. ModuleDescription m (moduleDef);
  86. if (! m.isValid())
  87. return Result::fail ("Failed to load module manifest: " + moduleDef.getFullPathName());
  88. modules.add (new ModuleDescription (m));
  89. }
  90. else
  91. {
  92. for (DirectoryIterator iter (path, false, "*", File::findDirectories); iter.next();)
  93. {
  94. Result r = addAllModulesInFolder (iter.getFile().getLinkedTarget());
  95. if (r.failed())
  96. return r;
  97. }
  98. }
  99. return Result::ok();
  100. }
  101. static Array<File> getAllPossibleModulePaths (Project& project)
  102. {
  103. StringArray paths;
  104. for (Project::ExporterIterator exporter (project); exporter.next();)
  105. {
  106. if (exporter->mayCompileOnCurrentOS())
  107. {
  108. for (int i = 0; i < project.getModules().getNumModules(); ++i)
  109. {
  110. const String path (exporter->getPathForModuleString (project.getModules().getModuleID (i)));
  111. if (path.isNotEmpty())
  112. paths.addIfNotAlreadyThere (path);
  113. }
  114. String oldPath (exporter->getLegacyModulePath());
  115. if (oldPath.isNotEmpty())
  116. paths.addIfNotAlreadyThere (oldPath);
  117. }
  118. }
  119. Array<File> files;
  120. for (int i = 0; i < paths.size(); ++i)
  121. {
  122. const File f (project.resolveFilename (paths[i]));
  123. if (f.isDirectory())
  124. {
  125. files.add (f);
  126. if (f.getChildFile ("modules").isDirectory())
  127. files.addIfNotAlreadyThere (f.getChildFile ("modules"));
  128. }
  129. }
  130. return files;
  131. }
  132. Result ModuleList::scanAllKnownFolders (Project& project)
  133. {
  134. modules.clear();
  135. Result result (Result::ok());
  136. const Array<File> modulePaths (getAllPossibleModulePaths (project));
  137. for (int i = 0; i < modulePaths.size(); ++i)
  138. {
  139. result = addAllModulesInFolder (modulePaths.getReference(i));
  140. if (result.failed())
  141. break;
  142. }
  143. sort();
  144. return result;
  145. }
  146. bool ModuleList::loadFromWebsite()
  147. {
  148. modules.clear();
  149. URL baseURL ("http://www.juce.com/juce/modules");
  150. URL url (baseURL.getChildURL ("modulelist.php"));
  151. const ScopedPointer<InputStream> in (url.createInputStream (false, nullptr, nullptr, String::empty, 4000));
  152. if (in == nullptr)
  153. return false;
  154. var infoList (JSON::parse (in->readEntireStreamAsString()));
  155. if (! infoList.isArray())
  156. return false;
  157. const Array<var>* moduleList = infoList.getArray();
  158. for (int i = 0; i < moduleList->size(); ++i)
  159. {
  160. const var& m = moduleList->getReference(i);
  161. const String file (m [Ids::file].toString());
  162. if (file.isNotEmpty())
  163. {
  164. ModuleDescription lm (m [Ids::info]);
  165. if (lm.isValid())
  166. {
  167. lm.url = baseURL.getChildURL (file);
  168. modules.add (new ModuleDescription (lm));
  169. }
  170. }
  171. }
  172. sort();
  173. return true;
  174. }
  175. //==============================================================================
  176. LibraryModule::LibraryModule (const ModuleDescription& d)
  177. : moduleInfo (d)
  178. {
  179. }
  180. bool LibraryModule::isAUPluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_AU"); }
  181. bool LibraryModule::isVSTPluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST"); }
  182. File LibraryModule::getModuleHeaderFile (const File& folder) const
  183. {
  184. return folder.getChildFile (moduleInfo.getHeaderName());
  185. }
  186. //==============================================================================
  187. void LibraryModule::writeIncludes (ProjectSaver& projectSaver, OutputStream& out)
  188. {
  189. const File localModuleFolder (projectSaver.getLocalModuleFolder (getID()));
  190. const File localHeader (getModuleHeaderFile (localModuleFolder));
  191. localModuleFolder.createDirectory();
  192. if (projectSaver.project.getModules().shouldCopyModuleFilesLocally (getID()).getValue())
  193. {
  194. projectSaver.copyFolder (moduleInfo.getFolder(), localModuleFolder);
  195. }
  196. else
  197. {
  198. localModuleFolder.createDirectory();
  199. createLocalHeaderWrapper (projectSaver, getModuleHeaderFile (moduleInfo.getFolder()), localHeader);
  200. }
  201. out << CodeHelpers::createIncludeStatement (localHeader, projectSaver.getGeneratedCodeFolder()
  202. .getChildFile ("AppConfig.h")) << newLine;
  203. }
  204. static void writeGuardedInclude (OutputStream& out, StringArray paths, StringArray guards)
  205. {
  206. StringArray uniquePaths (paths);
  207. uniquePaths.removeDuplicates (false);
  208. if (uniquePaths.size() == 1)
  209. {
  210. out << "#include " << paths[0] << newLine;
  211. }
  212. else
  213. {
  214. for (int i = paths.size(); --i >= 0;)
  215. {
  216. for (int j = i; --j >= 0;)
  217. {
  218. if (paths[i] == paths[j] && guards[i] == guards[j])
  219. {
  220. paths.remove (i);
  221. guards.remove (i);
  222. }
  223. }
  224. }
  225. for (int i = 0; i < paths.size(); ++i)
  226. {
  227. out << (i == 0 ? "#if " : "#elif ") << guards[i] << newLine
  228. << " #include " << paths[i] << newLine;
  229. }
  230. out << "#else" << newLine
  231. << " #error \"This file is designed to be used in an Introjucer-generated project!\"" << newLine
  232. << "#endif" << newLine;
  233. }
  234. }
  235. void LibraryModule::createLocalHeaderWrapper (ProjectSaver& projectSaver, const File& originalHeader, const File& localHeader) const
  236. {
  237. Project& project = projectSaver.project;
  238. MemoryOutputStream out;
  239. out << "// This is an auto-generated file to redirect any included" << newLine
  240. << "// module headers to the correct external folder." << newLine
  241. << newLine;
  242. StringArray paths, guards;
  243. for (Project::ExporterIterator exporter (project); exporter.next();)
  244. {
  245. const RelativePath headerFromProject (exporter->getModuleFolderRelativeToProject (getID(), projectSaver)
  246. .getChildFile (originalHeader.getFileName()));
  247. const RelativePath fileFromHere (headerFromProject.rebased (project.getProjectFolder(),
  248. localHeader.getParentDirectory(), RelativePath::unknown));
  249. paths.add (fileFromHere.toUnixStyle().quoted());
  250. guards.add ("defined (" + exporter->getExporterIdentifierMacro() + ")");
  251. }
  252. writeGuardedInclude (out, paths, guards);
  253. out << newLine;
  254. projectSaver.replaceFileIfDifferent (localHeader, out);
  255. }
  256. //==============================================================================
  257. void LibraryModule::prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
  258. {
  259. Project& project = exporter.getProject();
  260. exporter.addToExtraSearchPaths (exporter.getModuleFolderRelativeToProject (getID(), projectSaver).getParentDirectory());
  261. const String extraDefs (moduleInfo.getPreprocessorDefs().trim());
  262. if (extraDefs.isNotEmpty())
  263. exporter.getExporterPreprocessorDefs() = exporter.getExporterPreprocessorDefsString() + "\n" + extraDefs;
  264. {
  265. Array<File> compiled;
  266. findAndAddCompiledCode (exporter, projectSaver, moduleInfo.getFolder(), compiled);
  267. if (project.getModules().shouldShowAllModuleFilesInProject (getID()).getValue())
  268. addBrowsableCode (exporter, projectSaver, compiled, moduleInfo.getFolder());
  269. }
  270. if (isVSTPluginHost (project))
  271. VSTHelpers::addVSTFolderToPath (exporter, exporter.extraSearchPaths);
  272. if (exporter.isXcode())
  273. {
  274. if (isAUPluginHost (project))
  275. exporter.xcodeFrameworks.addTokens ("AudioUnit CoreAudioKit", false);
  276. const String frameworks (moduleInfo.moduleInfo [exporter.isOSX() ? "OSXFrameworks" : "iOSFrameworks"].toString());
  277. exporter.xcodeFrameworks.addTokens (frameworks, ", ", String::empty);
  278. }
  279. else if (exporter.isLinux())
  280. {
  281. const String libs (moduleInfo.moduleInfo ["LinuxLibs"].toString());
  282. exporter.linuxLibs.addTokens (libs, ", ", String::empty);
  283. exporter.linuxLibs.trim();
  284. exporter.linuxLibs.sort (false);
  285. exporter.linuxLibs.removeDuplicates (false);
  286. }
  287. else if (exporter.isCodeBlocks())
  288. {
  289. const String libs (moduleInfo.moduleInfo ["mingwLibs"].toString());
  290. exporter.mingwLibs.addTokens (libs, ", ", String::empty);
  291. exporter.mingwLibs.trim();
  292. exporter.mingwLibs.sort (false);
  293. exporter.mingwLibs.removeDuplicates (false);
  294. }
  295. if (moduleInfo.isPluginClient())
  296. {
  297. if (shouldBuildVST (project).getValue()) VSTHelpers::prepareExporter (exporter, projectSaver);
  298. if (shouldBuildAU (project).getValue()) AUHelpers::prepareExporter (exporter, projectSaver);
  299. if (shouldBuildAAX (project).getValue()) AAXHelpers::prepareExporter (exporter, projectSaver);
  300. if (shouldBuildRTAS (project).getValue()) RTASHelpers::prepareExporter (exporter, projectSaver);
  301. }
  302. }
  303. void LibraryModule::createPropertyEditors (ProjectExporter& exporter, PropertyListBuilder& props) const
  304. {
  305. if (isVSTPluginHost (exporter.getProject())
  306. && ! (moduleInfo.isPluginClient() && shouldBuildVST (exporter.getProject()).getValue()))
  307. VSTHelpers::createVSTPathEditor (exporter, props);
  308. if (moduleInfo.isPluginClient())
  309. {
  310. if (shouldBuildVST (exporter.getProject()).getValue()) VSTHelpers::createPropertyEditors (exporter, props);
  311. if (shouldBuildRTAS (exporter.getProject()).getValue()) RTASHelpers::createPropertyEditors (exporter, props);
  312. if (shouldBuildAAX (exporter.getProject()).getValue()) AAXHelpers::createPropertyEditors (exporter, props);
  313. }
  314. }
  315. void LibraryModule::getConfigFlags (Project& project, OwnedArray<Project::ConfigFlag>& flags) const
  316. {
  317. const File header (getModuleHeaderFile (moduleInfo.getFolder()));
  318. jassert (header.exists());
  319. StringArray lines;
  320. header.readLines (lines);
  321. for (int i = 0; i < lines.size(); ++i)
  322. {
  323. String line (lines[i].trim());
  324. if (line.startsWith ("/**") && line.containsIgnoreCase ("Config:"))
  325. {
  326. ScopedPointer <Project::ConfigFlag> config (new Project::ConfigFlag());
  327. config->sourceModuleID = getID();
  328. config->symbol = line.fromFirstOccurrenceOf (":", false, false).trim();
  329. if (config->symbol.length() > 2)
  330. {
  331. ++i;
  332. while (! (lines[i].contains ("*/") || lines[i].contains ("@see")))
  333. {
  334. if (lines[i].trim().isNotEmpty())
  335. config->description = config->description.trim() + " " + lines[i].trim();
  336. ++i;
  337. }
  338. config->description = config->description.upToFirstOccurrenceOf ("*/", false, false);
  339. config->value.referTo (project.getConfigFlag (config->symbol));
  340. flags.add (config.release());
  341. }
  342. }
  343. }
  344. }
  345. //==============================================================================
  346. static bool exporterTargetMatches (const String& test, String target)
  347. {
  348. StringArray validTargets;
  349. validTargets.addTokens (target, ",;", "");
  350. validTargets.trim();
  351. validTargets.removeEmptyStrings();
  352. if (validTargets.size() == 0)
  353. return true;
  354. for (int i = validTargets.size(); --i >= 0;)
  355. {
  356. const String& targetName = validTargets[i];
  357. if (targetName == test
  358. || (targetName.startsWithChar ('!') && test != targetName.substring (1).trimStart()))
  359. return true;
  360. }
  361. return false;
  362. }
  363. bool LibraryModule::fileTargetMatches (ProjectExporter& exporter, const String& target)
  364. {
  365. if (exporter.isXcode()) return exporterTargetMatches ("xcode", target);
  366. if (exporter.isWindows()) return exporterTargetMatches ("msvc", target);
  367. if (exporter.isLinux()) return exporterTargetMatches ("linux", target);
  368. if (exporter.isAndroid()) return exporterTargetMatches ("android", target);
  369. if (exporter.isCodeBlocks()) return exporterTargetMatches ("mingw", target);
  370. return target.isEmpty();
  371. }
  372. struct FileSorter
  373. {
  374. static int compareElements (const File& f1, const File& f2)
  375. {
  376. return f1.getFileName().compareIgnoreCase (f2.getFileName());
  377. }
  378. };
  379. void LibraryModule::findWildcardMatches (const File& localModuleFolder, const String& wildcardPath, Array<File>& result) const
  380. {
  381. String path (wildcardPath.upToLastOccurrenceOf ("/", false, false));
  382. String wildCard (wildcardPath.fromLastOccurrenceOf ("/", false, false));
  383. Array<File> tempList;
  384. FileSorter sorter;
  385. DirectoryIterator iter (localModuleFolder.getChildFile (path), false, wildCard);
  386. bool isHiddenFile;
  387. while (iter.next (nullptr, &isHiddenFile, nullptr, nullptr, nullptr, nullptr))
  388. if (! isHiddenFile)
  389. tempList.addSorted (sorter, iter.getFile());
  390. result.addArray (tempList);
  391. }
  392. void LibraryModule::findAndAddCompiledCode (ProjectExporter& exporter, ProjectSaver& projectSaver,
  393. const File& localModuleFolder, Array<File>& result) const
  394. {
  395. const var compileArray (moduleInfo.moduleInfo ["compile"]); // careful to keep this alive while the array is in use!
  396. if (const Array<var>* const files = compileArray.getArray())
  397. {
  398. for (int i = 0; i < files->size(); ++i)
  399. {
  400. const var& file = files->getReference(i);
  401. const String filename (file ["file"].toString());
  402. if (filename.isNotEmpty()
  403. && fileTargetMatches (exporter, file ["target"].toString()))
  404. {
  405. const File compiledFile (localModuleFolder.getChildFile (filename));
  406. result.add (compiledFile);
  407. Project::Item item (projectSaver.addFileToGeneratedGroup (compiledFile));
  408. if (file ["warnings"].toString().equalsIgnoreCase ("disabled"))
  409. item.getShouldInhibitWarningsValue() = true;
  410. if (file ["stdcall"])
  411. item.getShouldUseStdCallValue() = true;
  412. }
  413. }
  414. }
  415. }
  416. void LibraryModule::getLocalCompiledFiles (const File& localModuleFolder, Array<File>& result) const
  417. {
  418. const var compileArray (moduleInfo.moduleInfo ["compile"]); // careful to keep this alive while the array is in use!
  419. if (const Array<var>* const files = compileArray.getArray())
  420. {
  421. for (int i = 0; i < files->size(); ++i)
  422. {
  423. const var& file = files->getReference(i);
  424. const String filename (file ["file"].toString());
  425. if (filename.isNotEmpty()
  426. #if JUCE_MAC
  427. && exporterTargetMatches ("xcode", file ["target"].toString())
  428. #elif JUCE_WINDOWS
  429. && exporterTargetMatches ("msvc", file ["target"].toString())
  430. #elif JUCE_LINUX
  431. && exporterTargetMatches ("linux", file ["target"].toString())
  432. #endif
  433. )
  434. {
  435. result.add (localModuleFolder.getChildFile (filename));
  436. }
  437. }
  438. }
  439. }
  440. static void addFileWithGroups (Project::Item& group, const RelativePath& file, const String& path)
  441. {
  442. const int slash = path.indexOfChar (File::separator);
  443. if (slash >= 0)
  444. {
  445. const String topLevelGroup (path.substring (0, slash));
  446. const String remainingPath (path.substring (slash + 1));
  447. Project::Item newGroup (group.getOrCreateSubGroup (topLevelGroup));
  448. addFileWithGroups (newGroup, file, remainingPath);
  449. }
  450. else
  451. {
  452. if (! group.containsChildForFile (file))
  453. group.addRelativeFile (file, -1, false);
  454. }
  455. }
  456. void LibraryModule::findBrowseableFiles (const File& localModuleFolder, Array<File>& filesFound) const
  457. {
  458. const var filesArray (moduleInfo.moduleInfo ["browse"]);
  459. if (const Array<var>* const files = filesArray.getArray())
  460. for (int i = 0; i < files->size(); ++i)
  461. findWildcardMatches (localModuleFolder, files->getReference(i), filesFound);
  462. }
  463. void LibraryModule::addBrowsableCode (ProjectExporter& exporter, ProjectSaver& projectSaver,
  464. const Array<File>& compiled, const File& localModuleFolder) const
  465. {
  466. if (sourceFiles.size() == 0)
  467. findBrowseableFiles (localModuleFolder, sourceFiles);
  468. Project::Item sourceGroup (Project::Item::createGroup (exporter.getProject(), getID(), "__mainsourcegroup" + getID()));
  469. const RelativePath moduleFromProject (exporter.getModuleFolderRelativeToProject (getID(), projectSaver));
  470. for (int i = 0; i < sourceFiles.size(); ++i)
  471. {
  472. const String pathWithinModule (FileHelpers::getRelativePathFrom (sourceFiles.getReference(i), localModuleFolder));
  473. // (Note: in exporters like MSVC we have to avoid adding the same file twice, even if one of those instances
  474. // is flagged as being excluded from the build, because this overrides the other and it fails to compile)
  475. if (exporter.canCopeWithDuplicateFiles() || ! compiled.contains (sourceFiles.getReference(i)))
  476. addFileWithGroups (sourceGroup,
  477. moduleFromProject.getChildFile (pathWithinModule),
  478. pathWithinModule);
  479. }
  480. sourceGroup.addFile (localModuleFolder.getChildFile (FileHelpers::getRelativePathFrom (moduleInfo.manifestFile,
  481. moduleInfo.getFolder())), -1, false);
  482. sourceGroup.addFile (getModuleHeaderFile (localModuleFolder), -1, false);
  483. exporter.getModulesGroup().state.addChild (sourceGroup.state.createCopy(), -1, nullptr);
  484. }
  485. //==============================================================================
  486. EnabledModuleList::EnabledModuleList (Project& p, const ValueTree& s)
  487. : project (p), state (s)
  488. {
  489. }
  490. ModuleDescription EnabledModuleList::getModuleInfo (const String& moduleID)
  491. {
  492. return ModuleDescription (getModuleInfoFile (moduleID));
  493. }
  494. bool EnabledModuleList::isModuleEnabled (const String& moduleID) const
  495. {
  496. for (int i = 0; i < state.getNumChildren(); ++i)
  497. if (state.getChild(i) [Ids::ID] == moduleID)
  498. return true;
  499. return false;
  500. }
  501. bool EnabledModuleList::isAudioPluginModuleMissing() const
  502. {
  503. return project.getProjectType().isAudioPlugin()
  504. && ! isModuleEnabled ("juce_audio_plugin_client");
  505. }
  506. Value EnabledModuleList::shouldShowAllModuleFilesInProject (const String& moduleID)
  507. {
  508. return state.getChildWithProperty (Ids::ID, moduleID)
  509. .getPropertyAsValue (Ids::showAllCode, getUndoManager());
  510. }
  511. File EnabledModuleList::getModuleInfoFile (const String& moduleID)
  512. {
  513. for (Project::ExporterIterator exporter (project); exporter.next();)
  514. {
  515. if (exporter->mayCompileOnCurrentOS())
  516. {
  517. const String path (exporter->getPathForModuleString (moduleID));
  518. if (path.isNotEmpty())
  519. {
  520. const File moduleFolder (project.resolveFilename (path));
  521. if (moduleFolder.exists())
  522. {
  523. File f (moduleFolder.getChildFile (ModuleDescription::getManifestFileName()));
  524. if (f.exists())
  525. return f;
  526. f = moduleFolder.getChildFile (moduleID)
  527. .getChildFile (ModuleDescription::getManifestFileName());
  528. if (f.exists())
  529. return f;
  530. f = moduleFolder.getChildFile ("modules")
  531. .getChildFile (moduleID)
  532. .getChildFile (ModuleDescription::getManifestFileName());
  533. if (f.exists())
  534. return f;
  535. }
  536. }
  537. }
  538. }
  539. return File::nonexistent;
  540. }
  541. File EnabledModuleList::getModuleFolder (const String& moduleID)
  542. {
  543. const File infoFile (getModuleInfoFile (moduleID));
  544. return infoFile.exists() ? infoFile.getParentDirectory()
  545. : File::nonexistent;
  546. }
  547. struct ModuleTreeSorter
  548. {
  549. static int compareElements (const ValueTree& m1, const ValueTree& m2)
  550. {
  551. return m1[Ids::ID].toString().compareIgnoreCase (m2[Ids::ID]);
  552. }
  553. };
  554. void EnabledModuleList::sortAlphabetically()
  555. {
  556. ModuleTreeSorter sorter;
  557. state.sort (sorter, getUndoManager(), false);
  558. }
  559. Value EnabledModuleList::shouldCopyModuleFilesLocally (const String& moduleID) const
  560. {
  561. return state.getChildWithProperty (Ids::ID, moduleID)
  562. .getPropertyAsValue (Ids::useLocalCopy, getUndoManager());
  563. }
  564. void EnabledModuleList::addModule (const File& moduleManifestFile, bool copyLocally)
  565. {
  566. ModuleDescription info (moduleManifestFile);
  567. if (info.isValid())
  568. {
  569. const String moduleID (info.getID());
  570. if (! isModuleEnabled (moduleID))
  571. {
  572. ValueTree module (Ids::MODULES);
  573. module.setProperty (Ids::ID, moduleID, nullptr);
  574. state.addChild (module, -1, getUndoManager());
  575. sortAlphabetically();
  576. shouldShowAllModuleFilesInProject (moduleID) = true;
  577. shouldCopyModuleFilesLocally (moduleID) = copyLocally;
  578. String path (FileHelpers::getRelativePathFrom (moduleManifestFile.getParentDirectory().getParentDirectory(),
  579. project.getProjectFolder()));
  580. for (Project::ExporterIterator exporter (project); exporter.next();)
  581. exporter->getPathForModuleValue (moduleID) = path;
  582. }
  583. }
  584. }
  585. void EnabledModuleList::removeModule (const String& moduleID)
  586. {
  587. for (int i = state.getNumChildren(); --i >= 0;)
  588. if (state.getChild(i) [Ids::ID] == moduleID)
  589. state.removeChild (i, getUndoManager());
  590. for (Project::ExporterIterator exporter (project); exporter.next();)
  591. exporter->removePathForModule (moduleID);
  592. }
  593. void EnabledModuleList::createRequiredModules (OwnedArray<LibraryModule>& modules)
  594. {
  595. for (int i = 0; i < getNumModules(); ++i)
  596. {
  597. ModuleDescription info (getModuleInfo (getModuleID (i)));
  598. if (info.isValid())
  599. modules.add (new LibraryModule (info));
  600. }
  601. }
  602. StringArray EnabledModuleList::getAllModules() const
  603. {
  604. StringArray moduleIDs;
  605. for (int i = 0; i < getNumModules(); ++i)
  606. moduleIDs.add (getModuleID(i));
  607. return moduleIDs;
  608. }
  609. static void getDependencies (Project& project, const String& moduleID, StringArray& dependencies)
  610. {
  611. ModuleDescription info (project.getModules().getModuleInfo (moduleID));
  612. if (info.isValid())
  613. {
  614. const var depsArray (info.moduleInfo ["dependencies"]);
  615. if (const Array<var>* const deps = depsArray.getArray())
  616. {
  617. for (int i = 0; i < deps->size(); ++i)
  618. {
  619. const var& d = deps->getReference(i);
  620. String uid (d [Ids::ID].toString());
  621. String version (d [Ids::version].toString());
  622. if (! dependencies.contains (uid, true))
  623. {
  624. dependencies.add (uid);
  625. getDependencies (project, uid, dependencies);
  626. }
  627. }
  628. }
  629. }
  630. }
  631. StringArray EnabledModuleList::getExtraDependenciesNeeded (const String& moduleID) const
  632. {
  633. StringArray dependencies, extraDepsNeeded;
  634. getDependencies (project, moduleID, dependencies);
  635. for (int i = 0; i < dependencies.size(); ++i)
  636. if ((! isModuleEnabled (dependencies[i])) && dependencies[i] != moduleID)
  637. extraDepsNeeded.add (dependencies[i]);
  638. return extraDepsNeeded;
  639. }
  640. bool EnabledModuleList::areMostModulesCopiedLocally() const
  641. {
  642. int numYes = 0, numNo = 0;
  643. for (int i = getNumModules(); --i >= 0;)
  644. {
  645. if (shouldCopyModuleFilesLocally (getModuleID (i)).getValue())
  646. ++numYes;
  647. else
  648. ++numNo;
  649. }
  650. return numYes > numNo;
  651. }
  652. void EnabledModuleList::setLocalCopyModeForAllModules (bool copyLocally)
  653. {
  654. for (int i = getNumModules(); --i >= 0;)
  655. shouldCopyModuleFilesLocally (project.getModules().getModuleID (i)) = copyLocally;
  656. }
  657. File EnabledModuleList::findDefaultModulesFolder (Project& project)
  658. {
  659. ModuleList available;
  660. available.scanAllKnownFolders (project);
  661. for (int i = available.modules.size(); --i >= 0;)
  662. {
  663. File f (available.modules.getUnchecked(i)->getFolder());
  664. if (f.isDirectory())
  665. return f.getParentDirectory();
  666. }
  667. return File::getCurrentWorkingDirectory();
  668. }
  669. void EnabledModuleList::addModuleFromUserSelectedFile()
  670. {
  671. static File lastLocation (findDefaultModulesFolder (project));
  672. FileChooser fc ("Select a module to add...", lastLocation, String::empty, false);
  673. if (fc.browseForDirectory())
  674. {
  675. lastLocation = fc.getResult();
  676. addModuleOfferingToCopy (lastLocation);
  677. }
  678. }
  679. void EnabledModuleList::addModuleInteractive (const String& moduleID)
  680. {
  681. ModuleList list;
  682. list.scanAllKnownFolders (project);
  683. if (const ModuleDescription* info = list.getModuleWithID (moduleID))
  684. addModule (info->manifestFile, areMostModulesCopiedLocally());
  685. else
  686. addModuleFromUserSelectedFile();
  687. }
  688. void EnabledModuleList::addModuleOfferingToCopy (const File& f)
  689. {
  690. ModuleDescription m (f);
  691. if (! m.isValid())
  692. m = ModuleDescription (f.getChildFile (ModuleDescription::getManifestFileName()));
  693. if (! m.isValid())
  694. {
  695. AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
  696. "Add Module", "This wasn't a valid module folder!");
  697. return;
  698. }
  699. if (isModuleEnabled (m.getID()))
  700. {
  701. AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
  702. "Add Module", "The project already contains this module!");
  703. return;
  704. }
  705. addModule (m.manifestFile, areMostModulesCopiedLocally());
  706. }