1 /*
2   ==============================================================================
3 
4    This file is part of the JUCE library.
5    Copyright (c) 2020 - Raw Material Software Limited
6 
7    JUCE is an open source library subject to commercial or open-source
8    licensing.
9 
10    By using JUCE, you agree to the terms of both the JUCE 6 End-User License
11    Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020).
12 
13    End User License Agreement: www.juce.com/juce-6-licence
14    Privacy Policy: www.juce.com/juce-privacy-policy
15 
16    Or: You may also use this code under the terms of the GPL v3 (see
17    www.gnu.org/licenses).
18 
19    JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
20    EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
21    DISCLAIMED.
22 
23   ==============================================================================
24 */
25 
26 #include "../../Application/jucer_Headers.h"
27 #include "../../ProjectSaving/jucer_ProjectSaver.h"
28 #include "../../ProjectSaving/jucer_ProjectExport_Xcode.h"
29 #include "../../Application/jucer_Application.h"
30 
31 //==============================================================================
LibraryModule(const ModuleDescription & d)32 LibraryModule::LibraryModule (const ModuleDescription& d)
33     : moduleInfo (d)
34 {
35 }
36 
writeIncludes(ProjectSaver & projectSaver,OutputStream & out)37 void LibraryModule::writeIncludes (ProjectSaver& projectSaver, OutputStream& out)
38 {
39     auto& project = projectSaver.getProject();
40     auto& modules = project.getEnabledModules();
41 
42     auto moduleID = getID();
43 
44     if (modules.shouldCopyModuleFilesLocally (moduleID))
45     {
46         auto juceModuleFolder = moduleInfo.getFolder();
47 
48         auto localModuleFolder = project.getLocalModuleFolder (moduleID);
49         localModuleFolder.createDirectory();
50         projectSaver.copyFolder (juceModuleFolder, localModuleFolder);
51     }
52 
53     out << "#include <" << moduleInfo.getModuleFolder().getFileName() << "/"
54         << moduleInfo.getHeader().getFileName()
55         << ">" << newLine;
56 }
57 
addSearchPathsToExporter(ProjectExporter & exporter) const58 void LibraryModule::addSearchPathsToExporter (ProjectExporter& exporter) const
59 {
60     auto moduleRelativePath = exporter.getModuleFolderRelativeToProject (getID());
61 
62     exporter.addToExtraSearchPaths (moduleRelativePath.getParentDirectory());
63 
64     String libDirPlatform;
65 
66     if (exporter.isLinux())
67         libDirPlatform = "Linux";
68     else if (exporter.isCodeBlocks() && exporter.isWindows())
69         libDirPlatform = "MinGW";
70     else
71         libDirPlatform = exporter.getTargetFolder().getFileName();
72 
73     auto libSubdirPath = moduleRelativePath.toUnixStyle() + "/libs/" + libDirPlatform;
74     auto moduleLibDir = File (exporter.getProject().getProjectFolder().getFullPathName() + "/" + libSubdirPath);
75 
76     if (moduleLibDir.exists())
77         exporter.addToModuleLibPaths ({ libSubdirPath, moduleRelativePath.getRoot() });
78 
79     auto extraInternalSearchPaths = moduleInfo.getExtraSearchPaths().trim();
80 
81     if (extraInternalSearchPaths.isNotEmpty())
82     {
83         auto paths = StringArray::fromTokens (extraInternalSearchPaths, true);
84 
85         for (auto& path : paths)
86             exporter.addToExtraSearchPaths (moduleRelativePath.getChildFile (path.unquoted()));
87     }
88 }
89 
addDefinesToExporter(ProjectExporter & exporter) const90 void LibraryModule::addDefinesToExporter (ProjectExporter& exporter) const
91 {
92     auto extraDefs = moduleInfo.getPreprocessorDefs().trim();
93 
94     if (extraDefs.isNotEmpty())
95         exporter.getExporterPreprocessorDefsValue() = exporter.getExporterPreprocessorDefsString() + "\n" + extraDefs;
96 }
97 
addCompileUnitsToExporter(ProjectExporter & exporter,ProjectSaver & projectSaver) const98 void LibraryModule::addCompileUnitsToExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
99 {
100     auto& project = exporter.getProject();
101     auto& modules = project.getEnabledModules();
102 
103     auto moduleID = getID();
104 
105     auto localModuleFolder = modules.shouldCopyModuleFilesLocally (moduleID) ? project.getLocalModuleFolder (moduleID)
106                                                                              : moduleInfo.getFolder();
107 
108     Array<File> compiled;
109     findAndAddCompiledUnits (exporter, &projectSaver, compiled);
110 
111     if (modules.shouldShowAllModuleFilesInProject (moduleID))
112         addBrowseableCode (exporter, compiled, localModuleFolder);
113 }
114 
addLibsToExporter(ProjectExporter & exporter) const115 void LibraryModule::addLibsToExporter (ProjectExporter& exporter) const
116 {
117     auto parseAndAddLibsToList = [] (StringArray& libList, const String& libs)
118     {
119         libList.addTokens (libs, ", ", {});
120         libList.trim();
121         libList.removeDuplicates (false);
122     };
123 
124     auto& project = exporter.getProject();
125 
126     if (exporter.isXcode())
127     {
128         auto& xcodeExporter = dynamic_cast<XcodeProjectExporter&> (exporter);
129 
130         if (project.isAUPluginHost())
131         {
132             xcodeExporter.xcodeFrameworks.add ("CoreAudioKit");
133 
134             if (xcodeExporter.isOSX())
135                 xcodeExporter.xcodeFrameworks.add ("AudioUnit");
136         }
137 
138         auto frameworks = moduleInfo.getModuleInfo() [xcodeExporter.isOSX() ? "OSXFrameworks" : "iOSFrameworks"].toString();
139         xcodeExporter.xcodeFrameworks.addTokens (frameworks, ", ", {});
140 
141         parseAndAddLibsToList (xcodeExporter.xcodeLibs, moduleInfo.getModuleInfo() [exporter.isOSX() ? "OSXLibs" : "iOSLibs"].toString());
142     }
143     else if (exporter.isLinux())
144     {
145         parseAndAddLibsToList (exporter.linuxLibs, moduleInfo.getModuleInfo() ["linuxLibs"].toString());
146         parseAndAddLibsToList (exporter.linuxPackages, moduleInfo.getModuleInfo() ["linuxPackages"].toString());
147     }
148     else if (exporter.isWindows())
149     {
150         if (exporter.isCodeBlocks())
151             parseAndAddLibsToList (exporter.mingwLibs, moduleInfo.getModuleInfo() ["mingwLibs"].toString());
152         else
153             parseAndAddLibsToList (exporter.windowsLibs, moduleInfo.getModuleInfo() ["windowsLibs"].toString());
154     }
155     else if (exporter.isAndroid())
156     {
157         parseAndAddLibsToList (exporter.androidLibs, moduleInfo.getModuleInfo() ["androidLibs"].toString());
158     }
159 }
160 
addSettingsForModuleToExporter(ProjectExporter & exporter,ProjectSaver & projectSaver) const161 void LibraryModule::addSettingsForModuleToExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
162 {
163     addSearchPathsToExporter (exporter);
164     addDefinesToExporter (exporter);
165     addCompileUnitsToExporter (exporter, projectSaver);
166     addLibsToExporter (exporter);
167 }
168 
getConfigFlags(Project & project,OwnedArray<Project::ConfigFlag> & flags) const169 void LibraryModule::getConfigFlags (Project& project, OwnedArray<Project::ConfigFlag>& flags) const
170 {
171     auto header = moduleInfo.getHeader();
172     jassert (header.exists());
173 
174     StringArray lines;
175     header.readLines (lines);
176 
177     for (int i = 0; i < lines.size(); ++i)
178     {
179         auto line = lines[i].trim();
180 
181         if (line.startsWith ("/**") && line.containsIgnoreCase ("Config:"))
182         {
183             auto config = std::make_unique<Project::ConfigFlag>();
184             config->sourceModuleID = getID();
185             config->symbol = line.fromFirstOccurrenceOf (":", false, false).trim();
186 
187             if (config->symbol.length() > 2)
188             {
189                 ++i;
190 
191                 while (! (lines[i].contains ("*/") || lines[i].contains ("@see")))
192                 {
193                     if (lines[i].trim().isNotEmpty())
194                         config->description = config->description.trim() + " " + lines[i].trim();
195 
196                     ++i;
197                 }
198 
199                 config->description = config->description.upToFirstOccurrenceOf ("*/", false, false);
200                 config->value = project.getConfigFlag (config->symbol);
201 
202                 i += 2;
203 
204                 if (lines[i].contains ("#define " + config->symbol))
205                 {
206                     auto value = lines[i].fromFirstOccurrenceOf ("#define " + config->symbol, false, true).trim();
207                     config->value.setDefault (value == "0" ? false : true);
208                 }
209 
210                 auto currentValue = config->value.get().toString();
211 
212                 if      (currentValue == "enabled")     config->value = true;
213                 else if (currentValue == "disabled")    config->value = false;
214 
215                 flags.add (std::move (config));
216             }
217         }
218     }
219 }
220 
addFileWithGroups(Project::Item & group,const build_tools::RelativePath & file,const String & path)221 static void addFileWithGroups (Project::Item& group, const build_tools::RelativePath& file, const String& path)
222 {
223     auto slash = path.indexOfChar (File::getSeparatorChar());
224 
225     if (slash >= 0)
226     {
227         auto topLevelGroup = path.substring (0, slash);
228         auto remainingPath = path.substring (slash + 1);
229 
230         auto newGroup = group.getOrCreateSubGroup (topLevelGroup);
231         addFileWithGroups (newGroup, file, remainingPath);
232     }
233     else
234     {
235         if (! group.containsChildForFile (file))
236             group.addRelativeFile (file, -1, false);
237     }
238 }
239 
240 struct FileSorter
241 {
compareElementsFileSorter242     static int compareElements (const File& f1, const File& f2)
243     {
244         return f1.getFileName().compareNatural (f2.getFileName());
245     }
246 };
247 
findBrowseableFiles(const File & folder,Array<File> & filesFound) const248 void LibraryModule::findBrowseableFiles (const File& folder, Array<File>& filesFound) const
249 {
250     Array<File> tempList;
251     FileSorter sorter;
252 
253     for (const auto& iter : RangedDirectoryIterator (folder, true, "*", File::findFiles))
254         if (! iter.isHidden() && iter.getFile().hasFileExtension (browseableFileExtensions))
255             tempList.addSorted (sorter, iter.getFile());
256 
257     filesFound.addArray (tempList);
258 }
259 
isNeededForExporter(ProjectExporter & exporter) const260 bool LibraryModule::CompileUnit::isNeededForExporter (ProjectExporter& exporter) const
261 {
262     if ((hasSuffix (file, "_OSX")        && ! exporter.isOSX())
263      || (hasSuffix (file, "_iOS")        && ! exporter.isiOS())
264      || (hasSuffix (file, "_Windows")    && ! exporter.isWindows())
265      || (hasSuffix (file, "_Linux")      && ! exporter.isLinux())
266      || (hasSuffix (file, "_Android")    && ! exporter.isAndroid()))
267         return false;
268 
269     auto targetType = Project::getTargetTypeFromFilePath (file, false);
270 
271     if (targetType != build_tools::ProjectType::Target::unspecified && ! exporter.shouldBuildTargetType (targetType))
272         return false;
273 
274     return exporter.usesMMFiles() ? isCompiledForObjC
275                                   : isCompiledForNonObjC;
276 }
277 
getFilenameForProxyFile() const278 String LibraryModule::CompileUnit::getFilenameForProxyFile() const
279 {
280     return "include_" + file.getFileName();
281 }
282 
hasSuffix(const File & f,const char * suffix)283 bool LibraryModule::CompileUnit::hasSuffix (const File& f, const char* suffix)
284 {
285     auto fileWithoutSuffix = f.getFileNameWithoutExtension() + ".";
286 
287     return fileWithoutSuffix.containsIgnoreCase (suffix + String ("."))
288              || fileWithoutSuffix.containsIgnoreCase (suffix + String ("_"));
289 }
290 
getAllCompileUnits(build_tools::ProjectType::Target::Type forTarget) const291 Array<LibraryModule::CompileUnit> LibraryModule::getAllCompileUnits (build_tools::ProjectType::Target::Type forTarget) const
292 {
293     auto files = getFolder().findChildFiles (File::findFiles, false);
294 
295     FileSorter sorter;
296     files.sort (sorter);
297 
298     Array<LibraryModule::CompileUnit> units;
299 
300     for (auto& file : files)
301     {
302         if (file.getFileName().startsWithIgnoreCase (getID())
303               && file.hasFileExtension (sourceFileExtensions))
304         {
305             if (forTarget == build_tools::ProjectType::Target::unspecified
306              || forTarget == Project::getTargetTypeFromFilePath (file, true))
307             {
308                 CompileUnit cu;
309                 cu.file = file;
310                 units.add (std::move (cu));
311             }
312         }
313     }
314 
315     for (auto& cu : units)
316     {
317         cu.isCompiledForObjC = true;
318         cu.isCompiledForNonObjC = ! cu.file.hasFileExtension ("mm;m;metal");
319 
320         if (cu.isCompiledForNonObjC)
321             if (cu.file.withFileExtension ("mm").existsAsFile())
322                 cu.isCompiledForObjC = false;
323 
324         jassert (cu.isCompiledForObjC || cu.isCompiledForNonObjC);
325     }
326 
327     return units;
328 }
329 
findAndAddCompiledUnits(ProjectExporter & exporter,ProjectSaver * projectSaver,Array<File> & result,build_tools::ProjectType::Target::Type forTarget) const330 void LibraryModule::findAndAddCompiledUnits (ProjectExporter& exporter,
331                                              ProjectSaver* projectSaver,
332                                              Array<File>& result,
333                                              build_tools::ProjectType::Target::Type forTarget) const
334 {
335     for (auto& cu : getAllCompileUnits (forTarget))
336     {
337         if (cu.isNeededForExporter (exporter))
338         {
339             auto localFile = exporter.getProject().getGeneratedCodeFolder()
340                                                   .getChildFile (cu.getFilenameForProxyFile());
341             result.add (localFile);
342 
343             if (projectSaver != nullptr)
344                 projectSaver->addFileToGeneratedGroup (localFile);
345         }
346     }
347 }
348 
addBrowseableCode(ProjectExporter & exporter,const Array<File> & compiled,const File & localModuleFolder) const349 void LibraryModule::addBrowseableCode (ProjectExporter& exporter, const Array<File>& compiled, const File& localModuleFolder) const
350 {
351     if (sourceFiles.isEmpty())
352         findBrowseableFiles (localModuleFolder, sourceFiles);
353 
354     auto sourceGroup       = Project::Item::createGroup (exporter.getProject(), getID(), "__mainsourcegroup" + getID(), false);
355     auto moduleFromProject = exporter.getModuleFolderRelativeToProject (getID());
356     auto moduleHeader      = moduleInfo.getHeader();
357 
358     auto& project = exporter.getProject();
359 
360     if (project.getEnabledModules().shouldCopyModuleFilesLocally (getID()))
361         moduleHeader = project.getLocalModuleFolder (getID()).getChildFile (moduleHeader.getFileName());
362 
363     auto isModuleHeader = [&] (const File& f)  { return f.getFileName() == moduleHeader.getFileName(); };
364 
365     for (auto& sourceFile : sourceFiles)
366     {
367         auto pathWithinModule = build_tools::getRelativePathFrom (sourceFile, localModuleFolder);
368 
369         // (Note: in exporters like MSVC we have to avoid adding the same file twice, even if one of those instances
370         // is flagged as being excluded from the build, because this overrides the other and it fails to compile)
371         if ((exporter.canCopeWithDuplicateFiles() || ! compiled.contains (sourceFile)) && ! isModuleHeader (sourceFile))
372             addFileWithGroups (sourceGroup, moduleFromProject.getChildFile (pathWithinModule), pathWithinModule);
373     }
374 
375     sourceGroup.sortAlphabetically (true, true);
376     sourceGroup.addFileAtIndex (moduleHeader, -1, false);
377 
378     exporter.getModulesGroup().state.appendChild (sourceGroup.state.createCopy(), nullptr);
379 }
380 
381 //==============================================================================
EnabledModulesList(Project & p,const ValueTree & s)382 EnabledModulesList::EnabledModulesList (Project& p, const ValueTree& s)
383     : project (p), state (s)
384 {
385 }
386 
getAllModules() const387 StringArray EnabledModulesList::getAllModules() const
388 {
389     StringArray moduleIDs;
390 
391     for (int i = 0; i < getNumModules(); ++i)
392         moduleIDs.add (getModuleID (i));
393 
394     return moduleIDs;
395 }
396 
createRequiredModules(OwnedArray<LibraryModule> & modules)397 void EnabledModulesList::createRequiredModules (OwnedArray<LibraryModule>& modules)
398 {
399     for (int i = 0; i < getNumModules(); ++i)
400         modules.add (new LibraryModule (getModuleInfo (getModuleID (i))));
401 }
402 
sortAlphabetically()403 void EnabledModulesList::sortAlphabetically()
404 {
405     struct ModuleTreeSorter
406     {
407         static int compareElements (const ValueTree& m1, const ValueTree& m2)
408         {
409             return m1[Ids::ID].toString().compareIgnoreCase (m2[Ids::ID]);
410         }
411     };
412 
413     ModuleTreeSorter sorter;
414 
415     const ScopedLock sl (stateLock);
416     state.sort (sorter, getUndoManager(), false);
417 }
418 
getDefaultModulesFolder() const419 File EnabledModulesList::getDefaultModulesFolder() const
420 {
421     File globalPath (getAppSettings().getStoredPath (Ids::defaultJuceModulePath, TargetOS::getThisOS()).get().toString());
422 
423     if (globalPath.exists())
424         return globalPath;
425 
426     for (auto& exporterPathModule : project.getExporterPathsModulesList().getAllModules())
427     {
428         auto f = exporterPathModule.second;
429 
430         if (f.isDirectory())
431             return f.getParentDirectory();
432     }
433 
434     return File::getCurrentWorkingDirectory();
435 }
436 
getModuleInfo(const String & moduleID) const437 ModuleDescription EnabledModulesList::getModuleInfo (const String& moduleID) const
438 {
439     return ModuleDescription (project.getModuleWithID (moduleID).second);
440 }
441 
isModuleEnabled(const String & moduleID) const442 bool EnabledModulesList::isModuleEnabled (const String& moduleID) const
443 {
444     const ScopedLock sl (stateLock);
445     return state.getChildWithProperty (Ids::ID, moduleID).isValid();
446 }
447 
getDependencies(Project & project,const String & moduleID,StringArray & dependencies)448 static void getDependencies (Project& project, const String& moduleID, StringArray& dependencies)
449 {
450     auto info = project.getEnabledModules().getModuleInfo (moduleID);
451 
452     for (auto uid : info.getDependencies())
453     {
454         if (! dependencies.contains (uid, true))
455         {
456             dependencies.add (uid);
457             getDependencies (project, uid, dependencies);
458         }
459     }
460 }
461 
getExtraDependenciesNeeded(const String & moduleID) const462 StringArray EnabledModulesList::getExtraDependenciesNeeded (const String& moduleID) const
463 {
464     StringArray dependencies, extraDepsNeeded;
465     getDependencies (project, moduleID, dependencies);
466 
467     for (auto dep : dependencies)
468         if (dep != moduleID && ! isModuleEnabled (dep))
469             extraDepsNeeded.add (dep);
470 
471     return extraDepsNeeded;
472 }
473 
tryToFixMissingDependencies(const String & moduleID)474 bool EnabledModulesList::tryToFixMissingDependencies (const String& moduleID)
475 {
476     auto copyLocally = areMostModulesCopiedLocally();
477     auto useGlobalPath = areMostModulesUsingGlobalPath();
478 
479     StringArray missing;
480 
481     for (auto missingModule : getExtraDependenciesNeeded (moduleID))
482     {
483         auto mod = project.getModuleWithID (missingModule);
484 
485         if (mod.second != File())
486             addModule (mod.second, copyLocally, useGlobalPath);
487         else
488             missing.add (missingModule);
489     }
490 
491     return (missing.size() == 0);
492 }
493 
doesModuleHaveHigherCppStandardThanProject(const String & moduleID) const494 bool EnabledModulesList::doesModuleHaveHigherCppStandardThanProject (const String& moduleID) const
495 {
496     auto projectCppStandard = project.getCppStandardString();
497 
498     if (projectCppStandard == Project::getCppStandardVars().getLast().toString())
499         return false;
500 
501     auto moduleCppStandard = getModuleInfo (moduleID).getMinimumCppStandard();
502 
503     return (moduleCppStandard.getIntValue() > projectCppStandard.getIntValue());
504 }
505 
shouldUseGlobalPath(const String & moduleID) const506 bool EnabledModulesList::shouldUseGlobalPath (const String& moduleID) const
507 {
508     const ScopedLock sl (stateLock);
509     return (bool) shouldUseGlobalPathValue (moduleID).getValue();
510 }
511 
shouldUseGlobalPathValue(const String & moduleID) const512 Value EnabledModulesList::shouldUseGlobalPathValue (const String& moduleID) const
513 {
514     const ScopedLock sl (stateLock);
515     return state.getChildWithProperty (Ids::ID, moduleID)
516                 .getPropertyAsValue (Ids::useGlobalPath, getUndoManager());
517 }
518 
shouldShowAllModuleFilesInProject(const String & moduleID) const519 bool EnabledModulesList::shouldShowAllModuleFilesInProject (const String& moduleID) const
520 {
521     return (bool) shouldShowAllModuleFilesInProjectValue (moduleID).getValue();
522 }
523 
shouldShowAllModuleFilesInProjectValue(const String & moduleID) const524 Value EnabledModulesList::shouldShowAllModuleFilesInProjectValue (const String& moduleID) const
525 {
526     const ScopedLock sl (stateLock);
527     return state.getChildWithProperty (Ids::ID, moduleID)
528                 .getPropertyAsValue (Ids::showAllCode, getUndoManager());
529 }
530 
shouldCopyModuleFilesLocally(const String & moduleID) const531 bool EnabledModulesList::shouldCopyModuleFilesLocally (const String& moduleID) const
532 {
533     return (bool) shouldCopyModuleFilesLocallyValue (moduleID).getValue();
534 }
535 
shouldCopyModuleFilesLocallyValue(const String & moduleID) const536 Value EnabledModulesList::shouldCopyModuleFilesLocallyValue (const String& moduleID) const
537 {
538     const ScopedLock sl (stateLock);
539     return state.getChildWithProperty (Ids::ID, moduleID)
540                 .getPropertyAsValue (Ids::useLocalCopy, getUndoManager());
541 }
542 
areMostModulesUsingGlobalPath() const543 bool EnabledModulesList::areMostModulesUsingGlobalPath() const
544 {
545     int numYes = 0, numNo = 0;
546 
547     for (auto i = getNumModules(); --i >= 0;)
548     {
549         if (shouldUseGlobalPath (getModuleID (i)))
550             ++numYes;
551         else
552             ++numNo;
553     }
554 
555     return numYes > numNo;
556 }
557 
areMostModulesCopiedLocally() const558 bool EnabledModulesList::areMostModulesCopiedLocally() const
559 {
560     int numYes = 0, numNo = 0;
561 
562     for (auto i = getNumModules(); --i >= 0;)
563     {
564         if (shouldCopyModuleFilesLocally (getModuleID (i)))
565             ++numYes;
566         else
567             ++numNo;
568     }
569 
570     return numYes > numNo;
571 }
572 
getModulesWithHigherCppStandardThanProject() const573 StringArray EnabledModulesList::getModulesWithHigherCppStandardThanProject() const
574 {
575     StringArray list;
576 
577     for (auto& module : getAllModules())
578         if (doesModuleHaveHigherCppStandardThanProject (module))
579             list.add (module);
580 
581     return list;
582 }
583 
getModulesWithMissingDependencies() const584 StringArray EnabledModulesList::getModulesWithMissingDependencies() const
585 {
586     StringArray list;
587 
588     for (auto& module : getAllModules())
589         if (getExtraDependenciesNeeded (module).size() > 0)
590             list.add (module);
591 
592     return list;
593 }
594 
getHighestModuleCppStandard() const595 String EnabledModulesList::getHighestModuleCppStandard() const
596 {
597     auto highestCppStandard = Project::getCppStandardVars()[0].toString();
598 
599     for (auto& mod : getAllModules())
600     {
601         auto moduleCppStandard = getModuleInfo (mod).getMinimumCppStandard();
602 
603         if (moduleCppStandard == "latest")
604             return moduleCppStandard;
605 
606         if (moduleCppStandard.getIntValue() > highestCppStandard.getIntValue())
607             highestCppStandard = moduleCppStandard;
608     }
609 
610     return highestCppStandard;
611 }
612 
addModule(const File & moduleFolder,bool copyLocally,bool useGlobalPath)613 void EnabledModulesList::addModule (const File& moduleFolder, bool copyLocally, bool useGlobalPath)
614 {
615     ModuleDescription info (moduleFolder);
616 
617     if (info.isValid())
618     {
619         auto moduleID = info.getID();
620 
621         if (! isModuleEnabled (moduleID))
622         {
623             ValueTree module (Ids::MODULE);
624             module.setProperty (Ids::ID, moduleID, getUndoManager());
625 
626             {
627                 const ScopedLock sl (stateLock);
628                 state.appendChild (module, getUndoManager());
629             }
630 
631             sortAlphabetically();
632 
633             shouldShowAllModuleFilesInProjectValue (moduleID) = true;
634             shouldCopyModuleFilesLocallyValue (moduleID) = copyLocally;
635             shouldUseGlobalPathValue (moduleID) = useGlobalPath;
636 
637             build_tools::RelativePath path (moduleFolder.getParentDirectory(),
638                                             project.getProjectFolder(),
639                                             build_tools::RelativePath::projectFolder);
640 
641             for (Project::ExporterIterator exporter (project); exporter.next();)
642                 exporter->getPathForModuleValue (moduleID) = path.toUnixStyle();
643 
644             if (! useGlobalPath)
645                 project.rescanExporterPathModules (false);
646         }
647     }
648 }
649 
addModuleInteractive(const String & moduleID)650 void EnabledModulesList::addModuleInteractive (const String& moduleID)
651 {
652     auto f = project.getModuleWithID (moduleID).second;
653 
654     if (f != File())
655     {
656         addModule (f, areMostModulesCopiedLocally(), areMostModulesUsingGlobalPath());
657         return;
658     }
659 
660     addModuleFromUserSelectedFile();
661 }
662 
addModuleFromUserSelectedFile()663 void EnabledModulesList::addModuleFromUserSelectedFile()
664 {
665     auto lastLocation = getDefaultModulesFolder();
666 
667     FileChooser fc ("Select a module to add...", lastLocation, {});
668 
669     if (fc.browseForDirectory())
670     {
671         lastLocation = fc.getResult();
672         addModuleOfferingToCopy (lastLocation, true);
673     }
674 }
675 
addModuleOfferingToCopy(const File & f,bool isFromUserSpecifiedFolder)676 void EnabledModulesList::addModuleOfferingToCopy (const File& f, bool isFromUserSpecifiedFolder)
677 {
678     ModuleDescription m (f);
679 
680     if (! m.isValid())
681     {
682         AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
683                                           "Add Module", "This wasn't a valid module folder!");
684         return;
685     }
686 
687     if (isModuleEnabled (m.getID()))
688     {
689         AlertWindow::showMessageBoxAsync (AlertWindow::InfoIcon,
690                                           "Add Module", "The project already contains this module!");
691         return;
692     }
693 
694     addModule (m.getModuleFolder(), areMostModulesCopiedLocally(),
695                isFromUserSpecifiedFolder ? false : areMostModulesUsingGlobalPath());
696 }
697 
removeModule(String moduleID)698 void EnabledModulesList::removeModule (String moduleID) // must be pass-by-value, and not a const ref!
699 {
700     {
701         const ScopedLock sl (stateLock);
702 
703         for (auto i = state.getNumChildren(); --i >= 0;)
704             if (state.getChild(i) [Ids::ID] == moduleID)
705                 state.removeChild (i, getUndoManager());
706     }
707 
708     for (Project::ExporterIterator exporter (project); exporter.next();)
709         exporter->removePathForModule (moduleID);
710 }
711