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.

1004 lines
30KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../jucer_Headers.h"
  20. #include "jucer_Module.h"
  21. #include "jucer_ProjectType.h"
  22. #include "../Project Saving/jucer_ProjectExporter.h"
  23. #include "../Project Saving/jucer_ProjectSaver.h"
  24. #include "../Project Saving/jucer_ProjectExport_XCode.h"
  25. static String trimCommentCharsFromStartOfLine (const String& line)
  26. {
  27. return line.trimStart().trimCharactersAtStart ("*/").trimStart();
  28. }
  29. static var parseModuleDesc (const StringArray& lines)
  30. {
  31. DynamicObject* o = new DynamicObject();
  32. var result (o);
  33. for (int i = 0; i < lines.size(); ++i)
  34. {
  35. String line = trimCommentCharsFromStartOfLine (lines[i]);
  36. int colon = line.indexOfChar (':');
  37. if (colon >= 0)
  38. {
  39. String key = line.substring (0, colon).trim();
  40. String value = line.substring (colon + 1).trim();
  41. o->setProperty (key, value);
  42. }
  43. }
  44. return result;
  45. }
  46. static var parseModuleDesc (const File& header)
  47. {
  48. StringArray lines;
  49. header.readLines (lines);
  50. for (int i = 0; i < lines.size(); ++i)
  51. {
  52. if (trimCommentCharsFromStartOfLine (lines[i]).startsWith ("BEGIN_JUCE_MODULE_DECLARATION"))
  53. {
  54. StringArray desc;
  55. for (int j = i + 1; j < lines.size(); ++j)
  56. {
  57. if (trimCommentCharsFromStartOfLine (lines[j]).startsWith ("END_JUCE_MODULE_DECLARATION"))
  58. return parseModuleDesc (desc);
  59. desc.add (lines[j]);
  60. }
  61. break;
  62. }
  63. }
  64. return {};
  65. }
  66. ModuleDescription::ModuleDescription (const File& folder)
  67. : moduleFolder (folder),
  68. moduleInfo (parseModuleDesc (getHeader()))
  69. {
  70. }
  71. File ModuleDescription::getHeader() const
  72. {
  73. if (moduleFolder != File())
  74. {
  75. const char* extensions[] = { ".h", ".hpp", ".hxx" };
  76. for (auto e : extensions)
  77. {
  78. File header (moduleFolder.getChildFile (moduleFolder.getFileName() + e));
  79. if (header.existsAsFile())
  80. return header;
  81. }
  82. }
  83. return {};
  84. }
  85. StringArray ModuleDescription::getDependencies() const
  86. {
  87. auto deps = StringArray::fromTokens (moduleInfo ["dependencies"].toString(), " \t;,", "\"'");
  88. deps.trim();
  89. deps.removeEmptyStrings();
  90. return deps;
  91. }
  92. //==============================================================================
  93. ModuleList::ModuleList()
  94. {
  95. }
  96. ModuleList::ModuleList (const ModuleList& other)
  97. {
  98. operator= (other);
  99. }
  100. ModuleList& ModuleList::operator= (const ModuleList& other)
  101. {
  102. modules.clear();
  103. modules.addCopiesOf (other.modules);
  104. return *this;
  105. }
  106. const ModuleDescription* ModuleList::getModuleWithID (const String& moduleID) const
  107. {
  108. for (auto* m : modules)
  109. if (m->getID() == moduleID)
  110. return m;
  111. return nullptr;
  112. }
  113. struct ModuleSorter
  114. {
  115. static int compareElements (const ModuleDescription* m1, const ModuleDescription* m2)
  116. {
  117. return m1->getID().compareIgnoreCase (m2->getID());
  118. }
  119. };
  120. void ModuleList::sort()
  121. {
  122. ModuleSorter sorter;
  123. modules.sort (sorter);
  124. }
  125. StringArray ModuleList::getIDs() const
  126. {
  127. StringArray results;
  128. for (auto* m : modules)
  129. results.add (m->getID());
  130. results.sort (true);
  131. return results;
  132. }
  133. Result ModuleList::tryToAddModuleFromFolder (const File& path)
  134. {
  135. ModuleDescription m (path);
  136. if (m.isValid())
  137. {
  138. modules.add (new ModuleDescription (m));
  139. return Result::ok();
  140. }
  141. return Result::fail (path.getFullPathName() + " is not a valid module");
  142. }
  143. Result ModuleList::addAllModulesInFolder (const File& path)
  144. {
  145. if (! tryToAddModuleFromFolder (path))
  146. {
  147. const int subfolders = 2;
  148. return addAllModulesInSubfoldersRecursively (path, subfolders);
  149. }
  150. return Result::ok();
  151. }
  152. Result ModuleList::addAllModulesInSubfoldersRecursively (const File& path, int depth)
  153. {
  154. if (depth > 0)
  155. {
  156. for (DirectoryIterator iter (path, false, "*", File::findDirectories); iter.next();)
  157. {
  158. auto childPath = iter.getFile().getLinkedTarget();
  159. if (! tryToAddModuleFromFolder (childPath))
  160. addAllModulesInSubfoldersRecursively (childPath, depth - 1);
  161. }
  162. }
  163. return Result::ok();
  164. }
  165. static Array<File> getAllPossibleModulePathsFromExporters (Project& project)
  166. {
  167. StringArray paths;
  168. for (Project::ExporterIterator exporter (project); exporter.next();)
  169. {
  170. auto& modules = project.getModules();
  171. auto n = modules.getNumModules();
  172. for (int i = 0; i < n; ++i)
  173. {
  174. auto id = modules.getModuleID (i);
  175. if (modules.shouldUseGlobalPath (id).getValue())
  176. continue;
  177. const auto path = exporter->getPathForModuleString (id);
  178. if (path.isNotEmpty())
  179. paths.addIfNotAlreadyThere (path);
  180. }
  181. String oldPath (exporter->getLegacyModulePath());
  182. if (oldPath.isNotEmpty())
  183. paths.addIfNotAlreadyThere (oldPath);
  184. }
  185. Array<File> files;
  186. for (auto& path : paths)
  187. {
  188. auto f = project.resolveFilename (path);
  189. if (f.isDirectory())
  190. {
  191. files.addIfNotAlreadyThere (f);
  192. if (f.getChildFile ("modules").isDirectory())
  193. files.addIfNotAlreadyThere (f.getChildFile ("modules"));
  194. }
  195. }
  196. return files;
  197. }
  198. Result ModuleList::scanProjectExporterModulePaths (Project& project)
  199. {
  200. modules.clear();
  201. Result result (Result::ok());
  202. for (auto& m : getAllPossibleModulePathsFromExporters (project))
  203. {
  204. result = addAllModulesInFolder (m);
  205. if (result.failed())
  206. break;
  207. }
  208. sort();
  209. return result;
  210. }
  211. void ModuleList::scanGlobalJuceModulePath()
  212. {
  213. modules.clear();
  214. auto& settings = getAppSettings();
  215. auto path = settings.getStoredPath (Ids::defaultJuceModulePath).toString();
  216. if (path.isNotEmpty())
  217. addAllModulesInFolder ({ path });
  218. sort();
  219. }
  220. void ModuleList::scanGlobalUserModulePath()
  221. {
  222. modules.clear();
  223. auto paths = StringArray::fromTokens (getAppSettings().getStoredPath (Ids::defaultUserModulePath).toString(), ";", {});
  224. for (auto p : paths)
  225. {
  226. auto f = File::createFileWithoutCheckingPath (p.trim());
  227. if (f.exists())
  228. addAllModulesInFolder (f);
  229. }
  230. sort();
  231. }
  232. //==============================================================================
  233. LibraryModule::LibraryModule (const ModuleDescription& d)
  234. : moduleInfo (d)
  235. {
  236. }
  237. //==============================================================================
  238. void LibraryModule::writeIncludes (ProjectSaver& projectSaver, OutputStream& out)
  239. {
  240. Project& project = projectSaver.project;
  241. EnabledModuleList& modules = project.getModules();
  242. const String id (getID());
  243. if (modules.shouldCopyModuleFilesLocally (id).getValue())
  244. {
  245. const File juceModuleFolder (moduleInfo.getFolder());
  246. const File localModuleFolder (project.getLocalModuleFolder (id));
  247. localModuleFolder.createDirectory();
  248. projectSaver.copyFolder (juceModuleFolder, localModuleFolder);
  249. }
  250. out << "#include <" << moduleInfo.moduleFolder.getFileName() << "/"
  251. << moduleInfo.getHeader().getFileName()
  252. << ">" << newLine;
  253. }
  254. //==============================================================================
  255. static void parseAndAddLibs (StringArray& libList, const String& libs)
  256. {
  257. libList.addTokens (libs, ", ", {});
  258. libList.trim();
  259. libList.sort (false);
  260. libList.removeDuplicates (false);
  261. }
  262. void LibraryModule::addSettingsForModuleToExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
  263. {
  264. auto& project = exporter.getProject();
  265. const auto moduleRelativePath = exporter.getModuleFolderRelativeToProject (getID());
  266. exporter.addToExtraSearchPaths (moduleRelativePath.getParentDirectory());
  267. String libDirPlatform;
  268. if (exporter.isLinux())
  269. libDirPlatform = "Linux";
  270. else if (exporter.isCodeBlocks() && exporter.isWindows())
  271. libDirPlatform = "MinGW";
  272. else
  273. libDirPlatform = exporter.getTargetFolder().getFileName();
  274. const auto libSubdirPath = String (moduleRelativePath.toUnixStyle() + "/libs/") + libDirPlatform;
  275. const auto moduleLibDir = File (project.getProjectFolder().getFullPathName() + "/" + libSubdirPath);
  276. if (moduleLibDir.exists())
  277. exporter.addToModuleLibPaths (RelativePath (libSubdirPath, moduleRelativePath.getRoot()));
  278. const auto extraInternalSearchPaths = moduleInfo.getExtraSearchPaths().trim();
  279. if (extraInternalSearchPaths.isNotEmpty())
  280. {
  281. StringArray paths;
  282. paths.addTokens (extraInternalSearchPaths, true);
  283. for (int i = 0; i < paths.size(); ++i)
  284. exporter.addToExtraSearchPaths (moduleRelativePath.getChildFile (paths.getReference(i)));
  285. }
  286. {
  287. const String extraDefs (moduleInfo.getPreprocessorDefs().trim());
  288. if (extraDefs.isNotEmpty())
  289. exporter.getExporterPreprocessorDefs() = exporter.getExporterPreprocessorDefsString() + "\n" + extraDefs;
  290. }
  291. {
  292. Array<File> compiled;
  293. auto& modules = project.getModules();
  294. auto id = getID();
  295. const File localModuleFolder = modules.shouldCopyModuleFilesLocally (id).getValue()
  296. ? project.getLocalModuleFolder (id)
  297. : moduleInfo.getFolder();
  298. findAndAddCompiledUnits (exporter, &projectSaver, compiled);
  299. if (modules.shouldShowAllModuleFilesInProject (id).getValue())
  300. addBrowseableCode (exporter, compiled, localModuleFolder);
  301. }
  302. if (exporter.isXcode())
  303. {
  304. auto& xcodeExporter = dynamic_cast<XCodeProjectExporter&> (exporter);
  305. if (project.isAUPluginHost())
  306. xcodeExporter.xcodeFrameworks.addTokens (xcodeExporter.isOSX() ? "AudioUnit CoreAudioKit" : "CoreAudioKit", false);
  307. const String frameworks (moduleInfo.moduleInfo [xcodeExporter.isOSX() ? "OSXFrameworks" : "iOSFrameworks"].toString());
  308. xcodeExporter.xcodeFrameworks.addTokens (frameworks, ", ", {});
  309. parseAndAddLibs (xcodeExporter.xcodeLibs, moduleInfo.moduleInfo [exporter.isOSX() ? "OSXLibs" : "iOSLibs"].toString());
  310. }
  311. else if (exporter.isLinux())
  312. {
  313. parseAndAddLibs (exporter.linuxLibs, moduleInfo.moduleInfo ["linuxLibs"].toString());
  314. parseAndAddLibs (exporter.linuxPackages, moduleInfo.moduleInfo ["linuxPackages"].toString());
  315. }
  316. else if (exporter.isWindows())
  317. {
  318. if (exporter.isCodeBlocks())
  319. parseAndAddLibs (exporter.mingwLibs, moduleInfo.moduleInfo ["mingwLibs"].toString());
  320. else
  321. parseAndAddLibs (exporter.windowsLibs, moduleInfo.moduleInfo ["windowsLibs"].toString());
  322. }
  323. else if (exporter.isAndroid())
  324. {
  325. parseAndAddLibs (exporter.androidLibs, moduleInfo.moduleInfo ["androidLibs"].toString());
  326. }
  327. }
  328. void LibraryModule::getConfigFlags (Project& project, OwnedArray<Project::ConfigFlag>& flags) const
  329. {
  330. const File header (moduleInfo.getHeader());
  331. jassert (header.exists());
  332. StringArray lines;
  333. header.readLines (lines);
  334. for (int i = 0; i < lines.size(); ++i)
  335. {
  336. String line (lines[i].trim());
  337. if (line.startsWith ("/**") && line.containsIgnoreCase ("Config:"))
  338. {
  339. ScopedPointer<Project::ConfigFlag> config (new Project::ConfigFlag());
  340. config->sourceModuleID = getID();
  341. config->symbol = line.fromFirstOccurrenceOf (":", false, false).trim();
  342. if (config->symbol.length() > 2)
  343. {
  344. ++i;
  345. while (! (lines[i].contains ("*/") || lines[i].contains ("@see")))
  346. {
  347. if (lines[i].trim().isNotEmpty())
  348. config->description = config->description.trim() + " " + lines[i].trim();
  349. ++i;
  350. }
  351. config->description = config->description.upToFirstOccurrenceOf ("*/", false, false);
  352. config->value.referTo (project.getConfigFlag (config->symbol));
  353. flags.add (config.release());
  354. }
  355. }
  356. }
  357. }
  358. //==============================================================================
  359. struct FileSorter
  360. {
  361. static int compareElements (const File& f1, const File& f2)
  362. {
  363. return f1.getFileName().compareNatural (f2.getFileName());
  364. }
  365. };
  366. bool LibraryModule::CompileUnit::hasSuffix (const File& f, const char* suffix)
  367. {
  368. auto fileWithoutSuffix = f.getFileNameWithoutExtension() + ".";
  369. return fileWithoutSuffix.containsIgnoreCase (suffix + String ("."))
  370. || fileWithoutSuffix.containsIgnoreCase (suffix + String ("_"));
  371. }
  372. void LibraryModule::CompileUnit::writeInclude (MemoryOutputStream&) const
  373. {
  374. }
  375. bool LibraryModule::CompileUnit::isNeededForExporter (ProjectExporter& exporter) const
  376. {
  377. if ((hasSuffix (file, "_OSX") && ! exporter.isOSX())
  378. || (hasSuffix (file, "_iOS") && ! exporter.isiOS())
  379. || (hasSuffix (file, "_Windows") && ! exporter.isWindows())
  380. || (hasSuffix (file, "_Linux") && ! exporter.isLinux())
  381. || (hasSuffix (file, "_Android") && ! exporter.isAndroid()))
  382. return false;
  383. auto targetType = Project::getTargetTypeFromFilePath (file, false);
  384. if (targetType != ProjectType::Target::unspecified && ! exporter.shouldBuildTargetType (targetType))
  385. return false;
  386. return exporter.usesMMFiles() ? isCompiledForObjC
  387. : isCompiledForNonObjC;
  388. }
  389. String LibraryModule::CompileUnit::getFilenameForProxyFile() const
  390. {
  391. return "include_" + file.getFileName();
  392. }
  393. Array<LibraryModule::CompileUnit> LibraryModule::getAllCompileUnits (ProjectType::Target::Type forTarget) const
  394. {
  395. Array<File> files;
  396. getFolder().findChildFiles (files, File::findFiles, false);
  397. FileSorter sorter;
  398. files.sort (sorter);
  399. Array<LibraryModule::CompileUnit> units;
  400. for (auto& file : files)
  401. {
  402. if (file.getFileName().startsWithIgnoreCase (getID())
  403. && file.hasFileExtension (sourceFileExtensions))
  404. {
  405. if (forTarget == ProjectType::Target::unspecified
  406. || forTarget == Project::getTargetTypeFromFilePath (file, true))
  407. {
  408. CompileUnit cu;
  409. cu.file = file;
  410. units.add (cu);
  411. }
  412. }
  413. }
  414. for (auto& cu : units)
  415. {
  416. cu.isCompiledForObjC = true;
  417. cu.isCompiledForNonObjC = ! cu.file.hasFileExtension ("mm;m");
  418. if (cu.isCompiledForNonObjC)
  419. if (files.contains (cu.file.withFileExtension ("mm")))
  420. cu.isCompiledForObjC = false;
  421. jassert (cu.isCompiledForObjC || cu.isCompiledForNonObjC);
  422. }
  423. return units;
  424. }
  425. void LibraryModule::findAndAddCompiledUnits (ProjectExporter& exporter,
  426. ProjectSaver* projectSaver,
  427. Array<File>& result,
  428. ProjectType::Target::Type forTarget) const
  429. {
  430. for (auto& cu : getAllCompileUnits (forTarget))
  431. {
  432. if (cu.isNeededForExporter (exporter))
  433. {
  434. auto localFile = exporter.getProject().getGeneratedCodeFolder()
  435. .getChildFile (cu.getFilenameForProxyFile());
  436. result.add (localFile);
  437. if (projectSaver != nullptr)
  438. projectSaver->addFileToGeneratedGroup (localFile);
  439. }
  440. }
  441. }
  442. static void addFileWithGroups (Project::Item& group, const RelativePath& file, const String& path)
  443. {
  444. const int slash = path.indexOfChar (File::separator);
  445. if (slash >= 0)
  446. {
  447. const String topLevelGroup (path.substring (0, slash));
  448. const String remainingPath (path.substring (slash + 1));
  449. Project::Item newGroup (group.getOrCreateSubGroup (topLevelGroup));
  450. addFileWithGroups (newGroup, file, remainingPath);
  451. }
  452. else
  453. {
  454. if (! group.containsChildForFile (file))
  455. group.addRelativeFile (file, -1, false);
  456. }
  457. }
  458. void LibraryModule::findBrowseableFiles (const File& folder, Array<File>& filesFound) const
  459. {
  460. Array<File> tempList;
  461. FileSorter sorter;
  462. DirectoryIterator iter (folder, true, "*", File::findFiles);
  463. bool isHiddenFile;
  464. while (iter.next (nullptr, &isHiddenFile, nullptr, nullptr, nullptr, nullptr))
  465. if (! isHiddenFile && iter.getFile().hasFileExtension (browseableFileExtensions))
  466. tempList.addSorted (sorter, iter.getFile());
  467. filesFound.addArray (tempList);
  468. }
  469. void LibraryModule::addBrowseableCode (ProjectExporter& exporter, const Array<File>& compiled, const File& localModuleFolder) const
  470. {
  471. if (sourceFiles.isEmpty())
  472. findBrowseableFiles (localModuleFolder, sourceFiles);
  473. Project::Item sourceGroup (Project::Item::createGroup (exporter.getProject(), getID(), "__mainsourcegroup" + getID(), false));
  474. const RelativePath moduleFromProject (exporter.getModuleFolderRelativeToProject (getID()));
  475. auto moduleHeader = moduleInfo.getHeader();
  476. for (auto& sourceFile : sourceFiles)
  477. {
  478. auto pathWithinModule = FileHelpers::getRelativePathFrom (sourceFile, localModuleFolder);
  479. // (Note: in exporters like MSVC we have to avoid adding the same file twice, even if one of those instances
  480. // is flagged as being excluded from the build, because this overrides the other and it fails to compile)
  481. if ((exporter.canCopeWithDuplicateFiles() || ! compiled.contains (sourceFile)) && sourceFile != moduleHeader)
  482. addFileWithGroups (sourceGroup,
  483. moduleFromProject.getChildFile (pathWithinModule),
  484. pathWithinModule);
  485. }
  486. sourceGroup.sortAlphabetically (true, true);
  487. sourceGroup.addFileAtIndex (moduleHeader, -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 (getModuleFolder (moduleID));
  498. }
  499. bool EnabledModuleList::isModuleEnabled (const String& moduleID) const
  500. {
  501. return state.getChildWithProperty (Ids::ID, moduleID).isValid();
  502. }
  503. bool EnabledModuleList::isAudioPluginModuleMissing() const
  504. {
  505. return project.getProjectType().isAudioPlugin()
  506. && ! isModuleEnabled ("juce_audio_plugin_client");
  507. }
  508. Value EnabledModuleList::shouldUseGlobalPath (const String& moduleID) const
  509. {
  510. return state.getChildWithProperty (Ids::ID, moduleID)
  511. .getPropertyAsValue (Ids::useGlobalPath, getUndoManager());
  512. }
  513. Value EnabledModuleList::shouldShowAllModuleFilesInProject (const String& moduleID)
  514. {
  515. return state.getChildWithProperty (Ids::ID, moduleID)
  516. .getPropertyAsValue (Ids::showAllCode, getUndoManager());
  517. }
  518. File EnabledModuleList::getModuleFolderFromPathIfItExists (const String& path, const String& moduleID, const Project& project)
  519. {
  520. if (path.isNotEmpty())
  521. {
  522. auto moduleFolder = project.resolveFilename (path);
  523. if (moduleFolder.exists())
  524. {
  525. if (ModuleDescription (moduleFolder).isValid())
  526. return moduleFolder;
  527. auto f = moduleFolder.getChildFile (moduleID);
  528. if (ModuleDescription (f).isValid())
  529. return f;
  530. }
  531. }
  532. return {};
  533. }
  534. File EnabledModuleList::findUserModuleFolder (const String& possiblePaths, const String& moduleID)
  535. {
  536. auto paths = StringArray::fromTokens (possiblePaths, ";", {});
  537. for (auto p : paths)
  538. {
  539. auto f = File::createFileWithoutCheckingPath (p.trim());
  540. if (f.exists())
  541. {
  542. auto moduleFolder = getModuleFolderFromPathIfItExists (f.getFullPathName(), moduleID, project);
  543. if (moduleFolder != File())
  544. return moduleFolder;
  545. }
  546. }
  547. return {};
  548. }
  549. File EnabledModuleList::getModuleFolder (const String& moduleID)
  550. {
  551. if (shouldUseGlobalPath (moduleID).getValue())
  552. {
  553. if (isJuceModule (moduleID))
  554. return getModuleFolderFromPathIfItExists (getAppSettings().getStoredPath (Ids::defaultJuceModulePath).toString(), moduleID, project);
  555. return findUserModuleFolder (moduleID, getAppSettings().getStoredPath (Ids::defaultUserModulePath).toString());
  556. }
  557. auto paths = getAllPossibleModulePathsFromExporters (project);
  558. for (auto p : paths)
  559. {
  560. auto f = getModuleFolderFromPathIfItExists (p.getFullPathName(), moduleID, project);
  561. if (f != File())
  562. return f;
  563. }
  564. return {};
  565. }
  566. struct ModuleTreeSorter
  567. {
  568. static int compareElements (const ValueTree& m1, const ValueTree& m2)
  569. {
  570. return m1[Ids::ID].toString().compareIgnoreCase (m2[Ids::ID]);
  571. }
  572. };
  573. void EnabledModuleList::sortAlphabetically()
  574. {
  575. ModuleTreeSorter sorter;
  576. state.sort (sorter, getUndoManager(), false);
  577. }
  578. Value EnabledModuleList::shouldCopyModuleFilesLocally (const String& moduleID) const
  579. {
  580. return state.getChildWithProperty (Ids::ID, moduleID)
  581. .getPropertyAsValue (Ids::useLocalCopy, getUndoManager());
  582. }
  583. void EnabledModuleList::addModule (const File& moduleFolder, bool copyLocally, bool useGlobalPath)
  584. {
  585. ModuleDescription info (moduleFolder);
  586. if (info.isValid())
  587. {
  588. const String moduleID (info.getID());
  589. if (! isModuleEnabled (moduleID))
  590. {
  591. ValueTree module (Ids::MODULE);
  592. module.setProperty (Ids::ID, moduleID, nullptr);
  593. state.addChild (module, -1, getUndoManager());
  594. sortAlphabetically();
  595. shouldShowAllModuleFilesInProject (moduleID) = true;
  596. shouldCopyModuleFilesLocally (moduleID) = copyLocally;
  597. shouldUseGlobalPath (moduleID) = useGlobalPath;
  598. RelativePath path (moduleFolder.getParentDirectory(),
  599. project.getProjectFolder(), RelativePath::projectFolder);
  600. for (Project::ExporterIterator exporter (project); exporter.next();)
  601. exporter->getPathForModuleValue (moduleID) = path.toUnixStyle();
  602. }
  603. }
  604. }
  605. void EnabledModuleList::removeModule (String moduleID) // must be pass-by-value, and not a const ref!
  606. {
  607. for (int i = state.getNumChildren(); --i >= 0;)
  608. if (state.getChild(i) [Ids::ID] == moduleID)
  609. state.removeChild (i, getUndoManager());
  610. for (Project::ExporterIterator exporter (project); exporter.next();)
  611. exporter->removePathForModule (moduleID);
  612. }
  613. void EnabledModuleList::createRequiredModules (OwnedArray<LibraryModule>& modules)
  614. {
  615. for (int i = 0; i < getNumModules(); ++i)
  616. modules.add (new LibraryModule (getModuleInfo (getModuleID (i))));
  617. }
  618. StringArray EnabledModuleList::getAllModules() const
  619. {
  620. StringArray moduleIDs;
  621. for (int i = 0; i < getNumModules(); ++i)
  622. moduleIDs.add (getModuleID (i));
  623. return moduleIDs;
  624. }
  625. static void getDependencies (Project& project, const String& moduleID, StringArray& dependencies)
  626. {
  627. ModuleDescription info (project.getModules().getModuleInfo (moduleID));
  628. for (auto uid : info.getDependencies())
  629. {
  630. if (! dependencies.contains (uid, true))
  631. {
  632. dependencies.add (uid);
  633. getDependencies (project, uid, dependencies);
  634. }
  635. }
  636. }
  637. StringArray EnabledModuleList::getExtraDependenciesNeeded (const String& moduleID) const
  638. {
  639. StringArray dependencies, extraDepsNeeded;
  640. getDependencies (project, moduleID, dependencies);
  641. for (auto dep : dependencies)
  642. if (dep != moduleID && ! isModuleEnabled (dep))
  643. extraDepsNeeded.add (dep);
  644. return extraDepsNeeded;
  645. }
  646. bool EnabledModuleList::doesModuleHaveHigherCppStandardThanProject (const String& moduleID)
  647. {
  648. auto projectCppStandard = project.getCppStandardValue().toString();
  649. if (projectCppStandard == "latest")
  650. return false;
  651. auto moduleCppStandard = getModuleInfo (moduleID).getMinimumCppStandard();
  652. return (moduleCppStandard.getIntValue() > projectCppStandard.getIntValue());
  653. }
  654. bool EnabledModuleList::areMostModulesUsingGlobalPath() const
  655. {
  656. auto numYes = 0, numNo = 0;
  657. for (auto i = getNumModules(); --i >= 0;)
  658. {
  659. if (shouldUseGlobalPath (getModuleID (i)).getValue())
  660. ++numYes;
  661. else
  662. ++numNo;
  663. }
  664. return numYes > numNo;
  665. }
  666. bool EnabledModuleList::areMostModulesCopiedLocally() const
  667. {
  668. auto numYes = 0, numNo = 0;
  669. for (auto i = getNumModules(); --i >= 0;)
  670. {
  671. if (shouldCopyModuleFilesLocally (getModuleID (i)).getValue())
  672. ++numYes;
  673. else
  674. ++numNo;
  675. }
  676. return numYes > numNo;
  677. }
  678. void EnabledModuleList::setLocalCopyModeForAllModules (bool copyLocally)
  679. {
  680. for (int i = getNumModules(); --i >= 0;)
  681. shouldCopyModuleFilesLocally (project.getModules().getModuleID (i)) = copyLocally;
  682. }
  683. File EnabledModuleList::findGlobalModulesFolder()
  684. {
  685. auto& settings = getAppSettings();
  686. auto path = settings.getStoredPath (Ids::defaultJuceModulePath).toString();
  687. if (settings.isGlobalPathValid (File(), Ids::defaultJuceModulePath, path))
  688. return { path };
  689. return {};
  690. }
  691. File EnabledModuleList::findDefaultModulesFolder (Project& project)
  692. {
  693. auto globalPath = findGlobalModulesFolder();
  694. if (globalPath != File())
  695. return globalPath;
  696. ModuleList available;
  697. available.scanProjectExporterModulePaths (project);
  698. for (int i = available.modules.size(); --i >= 0;)
  699. {
  700. File f (available.modules.getUnchecked(i)->getFolder());
  701. if (f.isDirectory())
  702. return f.getParentDirectory();
  703. }
  704. return File::getCurrentWorkingDirectory();
  705. }
  706. bool EnabledModuleList::isJuceModule (const String& moduleID)
  707. {
  708. static StringArray juceModuleIds =
  709. {
  710. "juce_audio_basics",
  711. "juce_audio_devices",
  712. "juce_audio_formats",
  713. "juce_audio_plugin_client",
  714. "juce_audio_processors",
  715. "juce_audio_utils",
  716. "juce_blocks_basics",
  717. "juce_box2d",
  718. "juce_core",
  719. "juce_cryptography",
  720. "juce_data_structures",
  721. "juce_events",
  722. "juce_graphics",
  723. "juce_gui_basics",
  724. "juce_gui_extra",
  725. "juce_opengl",
  726. "juce_osc",
  727. "juce_product_unlocking",
  728. "juce_video"
  729. };
  730. return juceModuleIds.contains (moduleID);
  731. }
  732. void EnabledModuleList::addModuleFromUserSelectedFile()
  733. {
  734. static File lastLocation (findDefaultModulesFolder (project));
  735. FileChooser fc ("Select a module to add...", lastLocation, String());
  736. if (fc.browseForDirectory())
  737. {
  738. lastLocation = fc.getResult();
  739. addModuleOfferingToCopy (lastLocation, true);
  740. }
  741. }
  742. void EnabledModuleList::addModuleInteractive (const String& moduleID)
  743. {
  744. ModuleList list;
  745. list.scanGlobalJuceModulePath();
  746. if (auto* info = list.getModuleWithID (moduleID))
  747. {
  748. addModule (info->moduleFolder, areMostModulesCopiedLocally(), areMostModulesUsingGlobalPath());
  749. return;
  750. }
  751. list.scanGlobalUserModulePath();
  752. if (auto* info = list.getModuleWithID (moduleID))
  753. {
  754. addModule (info->moduleFolder, areMostModulesCopiedLocally(), areMostModulesUsingGlobalPath());
  755. return;
  756. }
  757. list.scanProjectExporterModulePaths (project);
  758. if (auto* info = list.getModuleWithID (moduleID))
  759. addModule (info->moduleFolder, areMostModulesCopiedLocally(), false);
  760. else
  761. addModuleFromUserSelectedFile();
  762. }
  763. void EnabledModuleList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder)
  764. {
  765. ModuleDescription m (f);
  766. if (! m.isValid())
  767. {
  768. AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
  769. "Add Module", "This wasn't a valid module folder!");
  770. return;
  771. }
  772. if (isModuleEnabled (m.getID()))
  773. {
  774. AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
  775. "Add Module", "The project already contains this module!");
  776. return;
  777. }
  778. addModule (m.moduleFolder, areMostModulesCopiedLocally(), isFromUserSpecifiedFolder ? false
  779. : areMostModulesUsingGlobalPath());
  780. }
  781. bool isJuceFolder (const File& f)
  782. {
  783. return isJuceModulesFolder (f.getChildFile ("modules"));
  784. }
  785. bool isJuceModulesFolder (const File& f)
  786. {
  787. return f.isDirectory() && f.getChildFile ("juce_core").isDirectory();
  788. }