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.

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