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.

929 lines
31KB

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