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.

678 lines
23KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library - "Jules' Utility Class Extensions"
  4. Copyright 2004-11 by Raw Material Software Ltd.
  5. ------------------------------------------------------------------------------
  6. JUCE can be redistributed and/or modified under the terms of the GNU General
  7. Public License (Version 2), as published by the Free Software Foundation.
  8. A copy of the license is included in the JUCE distribution, or can be found
  9. online at www.gnu.org/licenses.
  10. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  11. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  12. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  13. ------------------------------------------------------------------------------
  14. To release a closed-source product which uses JUCE, commercial licenses are
  15. available: visit www.rawmaterialsoftware.com/juce for more information.
  16. ==============================================================================
  17. */
  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. //==============================================================================
  24. ModuleList::ModuleList()
  25. {
  26. }
  27. ModuleList::ModuleList (const ModuleList& other)
  28. : moduleFolder (other.moduleFolder)
  29. {
  30. modules.addCopiesOf (other.modules);
  31. }
  32. ModuleList& ModuleList::operator= (const ModuleList& other)
  33. {
  34. moduleFolder = other.moduleFolder;
  35. modules.clear();
  36. modules.addCopiesOf (other.modules);
  37. return *this;
  38. }
  39. bool ModuleList::operator== (const ModuleList& other) const
  40. {
  41. if (modules.size() != other.modules.size())
  42. return false;
  43. for (int i = modules.size(); --i >= 0;)
  44. {
  45. const Module* m1 = modules.getUnchecked(i);
  46. const Module* m2 = other.findModuleInfo (m1->uid);
  47. if (m2 == nullptr || *m1 != *m2)
  48. return false;
  49. }
  50. return true;
  51. }
  52. bool ModuleList::isLocalModulesFolderValid()
  53. {
  54. return isModulesFolder (getModulesFolderForJuceOrModulesFolder (getLocalModulesFolder (nullptr)));
  55. }
  56. bool ModuleList::isJuceFolder (const File& folder)
  57. {
  58. return folder.getFileName().containsIgnoreCase ("juce")
  59. && isModulesFolder (folder.getChildFile ("modules"));
  60. }
  61. bool ModuleList::isModulesFolder (const File& folder)
  62. {
  63. return folder.getFileName().equalsIgnoreCase ("modules")
  64. && folder.isDirectory();
  65. }
  66. bool ModuleList::isJuceOrModulesFolder (const File& folder)
  67. {
  68. return isJuceFolder (folder) || isModulesFolder (folder);
  69. }
  70. File ModuleList::getModulesFolderForJuceOrModulesFolder (const File& f)
  71. {
  72. if (f.getFileName() != "modules" && f.isDirectory() && f.getChildFile ("modules").isDirectory())
  73. return f.getChildFile ("modules");
  74. return f;
  75. }
  76. File ModuleList::getDefaultModulesFolder (Project* project)
  77. {
  78. if (project != nullptr)
  79. {
  80. ScopedPointer <ProjectExporter> exp (ProjectExporter::createPlatformDefaultExporter (*project));
  81. if (exp != nullptr)
  82. {
  83. File f (project->resolveFilename (exp->getJuceFolder().toString()));
  84. f = getModulesFolderForJuceOrModulesFolder (f);
  85. if (ModuleList::isModulesFolder (f))
  86. return f;
  87. }
  88. }
  89. #if JUCE_WINDOWS
  90. return File::getSpecialLocation (File::userDocumentsDirectory)
  91. #else
  92. return File::getSpecialLocation (File::userHomeDirectory)
  93. #endif
  94. .getChildFile ("juce")
  95. .getChildFile ("modules");
  96. }
  97. File ModuleList::getLocalModulesFolder (Project* project)
  98. {
  99. File defaultJuceFolder (getDefaultModulesFolder (project));
  100. File f (StoredSettings::getInstance()->getProps().getValue ("lastJuceFolder", defaultJuceFolder.getFullPathName()));
  101. f = getModulesFolderForJuceOrModulesFolder (f);
  102. if ((! ModuleList::isModulesFolder (f)) && ModuleList::isModulesFolder (defaultJuceFolder))
  103. f = defaultJuceFolder;
  104. return f;
  105. }
  106. File ModuleList::getModuleFolder (const String& uid) const
  107. {
  108. return getModulesFolder().getChildFile (uid);
  109. }
  110. void ModuleList::setLocalModulesFolder (const File& file)
  111. {
  112. //jassert (FileHelpers::isJuceFolder (file));
  113. StoredSettings::getInstance()->getProps().setValue ("lastJuceFolder", file.getFullPathName());
  114. }
  115. struct ModuleSorter
  116. {
  117. static int compareElements (const ModuleList::Module* m1, const ModuleList::Module* m2)
  118. {
  119. return m1->uid.compareIgnoreCase (m2->uid);
  120. }
  121. };
  122. void ModuleList::sort()
  123. {
  124. ModuleSorter sorter;
  125. modules.sort (sorter);
  126. }
  127. void ModuleList::rescan()
  128. {
  129. rescan (moduleFolder);
  130. }
  131. void ModuleList::rescan (const File& newModulesFolder)
  132. {
  133. modules.clear();
  134. moduleFolder = getModulesFolderForJuceOrModulesFolder (newModulesFolder);
  135. if (moduleFolder.isDirectory())
  136. {
  137. DirectoryIterator iter (moduleFolder, false, "*", File::findDirectories);
  138. while (iter.next())
  139. {
  140. const File moduleDef (iter.getFile().getChildFile (LibraryModule::getInfoFileName()));
  141. if (moduleDef.exists())
  142. {
  143. LibraryModule m (moduleDef);
  144. jassert (m.isValid());
  145. if (m.isValid())
  146. {
  147. Module* info = new Module();
  148. modules.add (info);
  149. info->uid = m.getID();
  150. info->version = m.getVersion();
  151. info->name = m.moduleInfo ["name"];
  152. info->description = m.moduleInfo ["description"];
  153. info->file = moduleDef;
  154. }
  155. }
  156. }
  157. }
  158. sort();
  159. }
  160. bool ModuleList::loadFromWebsite()
  161. {
  162. modules.clear();
  163. URL baseURL ("http://www.rawmaterialsoftware.com/juce/modules");
  164. URL url (baseURL.getChildURL ("modulelist.php"));
  165. var infoList (JSON::parse (url.readEntireTextStream (false)));
  166. if (infoList.isArray())
  167. {
  168. const Array<var>* moduleList = infoList.getArray();
  169. for (int i = 0; i < moduleList->size(); ++i)
  170. {
  171. const var& m = moduleList->getReference(i);
  172. const String file (m ["file"].toString());
  173. if (file.isNotEmpty())
  174. {
  175. var moduleInfo (m ["info"]);
  176. LibraryModule lm (moduleInfo);
  177. if (lm.isValid())
  178. {
  179. Module* info = new Module();
  180. modules.add (info);
  181. info->uid = lm.getID();
  182. info->version = lm.getVersion();
  183. info->name = lm.getName();
  184. info->description = lm.getDescription();
  185. info->url = baseURL.getChildURL (file);
  186. }
  187. }
  188. }
  189. }
  190. sort();
  191. return infoList.isArray();
  192. }
  193. LibraryModule* ModuleList::Module::create() const
  194. {
  195. return new LibraryModule (file);
  196. }
  197. bool ModuleList::Module::operator== (const Module& other) const
  198. {
  199. return uid == other.uid
  200. && version == other.version
  201. && name == other.name
  202. && description == other.description
  203. && file == other.file
  204. && url == other.url;
  205. }
  206. bool ModuleList::Module::operator!= (const Module& other) const
  207. {
  208. return ! operator== (other);
  209. }
  210. LibraryModule* ModuleList::loadModule (const String& uid) const
  211. {
  212. const Module* const m = findModuleInfo (uid);
  213. return m != nullptr ? m->create() : nullptr;
  214. }
  215. const ModuleList::Module* ModuleList::findModuleInfo (const String& uid) const
  216. {
  217. for (int i = modules.size(); --i >= 0;)
  218. if (modules.getUnchecked(i)->uid == uid)
  219. return modules.getUnchecked(i);
  220. return nullptr;
  221. }
  222. void ModuleList::getDependencies (const String& moduleID, StringArray& dependencies) const
  223. {
  224. ScopedPointer<LibraryModule> m (loadModule (moduleID));
  225. if (m != nullptr)
  226. {
  227. const var depsArray (m->moduleInfo ["dependencies"]);
  228. const Array<var>* const deps = depsArray.getArray();
  229. if (deps != nullptr)
  230. {
  231. for (int i = 0; i < deps->size(); ++i)
  232. {
  233. const var& d = deps->getReference(i);
  234. String uid (d ["id"].toString());
  235. String version (d ["version"].toString());
  236. if (! dependencies.contains (uid, true))
  237. {
  238. dependencies.add (uid);
  239. getDependencies (uid, dependencies);
  240. }
  241. }
  242. }
  243. }
  244. }
  245. void ModuleList::createDependencies (const String& moduleID, OwnedArray<LibraryModule>& modules) const
  246. {
  247. ScopedPointer<LibraryModule> m (loadModule (moduleID));
  248. if (m != nullptr)
  249. {
  250. var depsArray (m->moduleInfo ["dependencies"]);
  251. const Array<var>* const deps = depsArray.getArray();
  252. for (int i = 0; i < deps->size(); ++i)
  253. {
  254. const var& d = deps->getReference(i);
  255. String uid (d ["id"].toString());
  256. String version (d ["version"].toString());
  257. //xxx to do - also need to find version conflicts
  258. jassertfalse
  259. }
  260. }
  261. }
  262. StringArray ModuleList::getExtraDependenciesNeeded (Project& project, const ModuleList::Module& m)
  263. {
  264. StringArray dependencies, extraDepsNeeded;
  265. getDependencies (m.uid, dependencies);
  266. for (int i = 0; i < dependencies.size(); ++i)
  267. if ((! project.isModuleEnabled (dependencies[i])) && dependencies[i] != m.uid)
  268. extraDepsNeeded.add (dependencies[i]);
  269. return extraDepsNeeded;
  270. }
  271. //==============================================================================
  272. LibraryModule::LibraryModule (const File& file)
  273. : moduleInfo (JSON::parse (file)),
  274. moduleFile (file),
  275. moduleFolder (file.getParentDirectory())
  276. {
  277. jassert (isValid());
  278. }
  279. LibraryModule::LibraryModule (const var& moduleInfo_)
  280. : moduleInfo (moduleInfo_)
  281. {
  282. }
  283. bool LibraryModule::isValid() const { return getID().isNotEmpty(); }
  284. bool LibraryModule::isPluginClient() const { return getID() == "juce_audio_plugin_client"; }
  285. bool LibraryModule::isAUPluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_AU"); }
  286. bool LibraryModule::isVSTPluginHost (const Project& project) const { return getID() == "juce_audio_processors" && project.isConfigFlagEnabled ("JUCE_PLUGINHOST_VST"); }
  287. File LibraryModule::getLocalIncludeFolder (ProjectSaver& projectSaver) const
  288. {
  289. return projectSaver.getGeneratedCodeFolder().getChildFile ("modules").getChildFile (getID());
  290. }
  291. File LibraryModule::getInclude (const File& folder) const
  292. {
  293. return folder.getChildFile (moduleInfo ["include"]);
  294. }
  295. RelativePath LibraryModule::getModuleRelativeToProject (ProjectExporter& exporter) const
  296. {
  297. RelativePath p (exporter.getJuceFolder().toString(), RelativePath::projectFolder);
  298. if (p.getFileName() != "modules")
  299. p = p.getChildFile ("modules");
  300. return p.getChildFile (getID());
  301. }
  302. RelativePath LibraryModule::getModuleOrLocalCopyRelativeToProject (ProjectExporter& exporter, const File& localModuleFolder) const
  303. {
  304. if (exporter.getProject().shouldCopyModuleFilesLocally (getID()).getValue())
  305. return RelativePath (exporter.getProject().getRelativePathForFile (localModuleFolder), RelativePath::projectFolder);
  306. return getModuleRelativeToProject (exporter);
  307. }
  308. //==============================================================================
  309. void LibraryModule::writeIncludes (ProjectSaver& projectSaver, OutputStream& out)
  310. {
  311. const File localModuleFolder (getLocalIncludeFolder (projectSaver));
  312. const File localHeader (getInclude (localModuleFolder));
  313. if (projectSaver.getProject().shouldCopyModuleFilesLocally (getID()).getValue())
  314. {
  315. moduleFolder.copyDirectoryTo (localModuleFolder);
  316. }
  317. else
  318. {
  319. localModuleFolder.createDirectory();
  320. createLocalHeaderWrapper (projectSaver, getInclude (moduleFolder), localHeader);
  321. }
  322. out << CodeHelpers::createIncludeStatement (localHeader, projectSaver.getGeneratedCodeFolder().getChildFile ("AppConfig.h")) << newLine;
  323. }
  324. static void writeGuardedInclude (OutputStream& out, StringArray paths, StringArray guards)
  325. {
  326. StringArray uniquePaths (paths);
  327. uniquePaths.removeDuplicates (false);
  328. if (uniquePaths.size() == 1)
  329. {
  330. out << "#include " << paths[0] << newLine;
  331. }
  332. else
  333. {
  334. int i = paths.size();
  335. for (; --i >= 0;)
  336. {
  337. for (int j = i; --j >= 0;)
  338. {
  339. if (paths[i] == paths[j] && guards[i] == guards[j])
  340. {
  341. paths.remove (i);
  342. guards.remove (i);
  343. }
  344. }
  345. }
  346. for (i = 0; i < paths.size(); ++i)
  347. {
  348. out << (i == 0 ? "#if " : "#elif ") << guards[i] << newLine
  349. << " #include " << paths[i] << newLine;
  350. }
  351. out << "#else" << newLine
  352. << " #error \"This file is designed to be used in an Introjucer-generated project!\"" << newLine
  353. << "#endif" << newLine;
  354. }
  355. }
  356. void LibraryModule::createLocalHeaderWrapper (ProjectSaver& projectSaver, const File& originalHeader, const File& localHeader) const
  357. {
  358. Project& project = projectSaver.getProject();
  359. MemoryOutputStream out;
  360. out << "// This is an auto-generated file to redirect any included" << newLine
  361. << "// module headers to the correct external folder." << newLine
  362. << newLine;
  363. StringArray paths, guards;
  364. for (Project::ExporterIterator exporter (project); exporter.next();)
  365. {
  366. const RelativePath headerFromProject (getModuleRelativeToProject (*exporter)
  367. .getChildFile (originalHeader.getFileName()));
  368. const RelativePath fileFromHere (headerFromProject.rebased (project.getFile().getParentDirectory(),
  369. localHeader.getParentDirectory(), RelativePath::unknown));
  370. paths.add (fileFromHere.toUnixStyle().quoted());
  371. guards.add ("defined (" + exporter->getExporterIdentifierMacro() + ")");
  372. }
  373. writeGuardedInclude (out, paths, guards);
  374. out << newLine;
  375. projectSaver.replaceFileIfDifferent (localHeader, out);
  376. }
  377. //==============================================================================
  378. void LibraryModule::prepareExporter (ProjectExporter& exporter, ProjectSaver& projectSaver) const
  379. {
  380. Project& project = exporter.getProject();
  381. File localFolder (moduleFolder);
  382. if (project.shouldCopyModuleFilesLocally (getID()).getValue())
  383. localFolder = getLocalIncludeFolder (projectSaver);
  384. {
  385. Array<File> compiled;
  386. findAndAddCompiledCode (exporter, projectSaver, localFolder, compiled);
  387. if (project.shouldShowAllModuleFilesInProject (getID()).getValue())
  388. addBrowsableCode (exporter, compiled, localFolder);
  389. }
  390. if (isVSTPluginHost (project))
  391. VSTHelpers::addVSTFolderToPath (exporter, exporter.extraSearchPaths);
  392. if (exporter.isXcode())
  393. {
  394. if (isAUPluginHost (project))
  395. exporter.xcodeFrameworks.addTokens ("AudioUnit CoreAudioKit", false);
  396. const String frameworks (moduleInfo [exporter.isOSX() ? "OSXFrameworks" : "iOSFrameworks"].toString());
  397. exporter.xcodeFrameworks.addTokens (frameworks, ", ", String::empty);
  398. }
  399. if (isPluginClient())
  400. {
  401. if (shouldBuildVST (project).getValue()) VSTHelpers::prepareExporter (exporter, projectSaver);
  402. if (shouldBuildRTAS (project).getValue()) RTASHelpers::prepareExporter (exporter, projectSaver, localFolder);
  403. if (shouldBuildAU (project).getValue()) AUHelpers::prepareExporter (exporter, projectSaver);
  404. }
  405. }
  406. void LibraryModule::createPropertyEditors (const ProjectExporter& exporter, PropertyListBuilder& props) const
  407. {
  408. if (isVSTPluginHost (exporter.getProject()))
  409. VSTHelpers::createVSTPathEditor (exporter, props);
  410. if (isPluginClient())
  411. {
  412. if (shouldBuildVST (exporter.getProject()).getValue()) VSTHelpers::createPropertyEditors (exporter, props);
  413. if (shouldBuildRTAS (exporter.getProject()).getValue()) RTASHelpers::createPropertyEditors (exporter, props);
  414. }
  415. }
  416. void LibraryModule::getConfigFlags (Project& project, OwnedArray<Project::ConfigFlag>& flags) const
  417. {
  418. const File header (getInclude (moduleFolder));
  419. jassert (header.exists());
  420. StringArray lines;
  421. header.readLines (lines);
  422. for (int i = 0; i < lines.size(); ++i)
  423. {
  424. String line (lines[i].trim());
  425. if (line.startsWith ("/**") && line.containsIgnoreCase ("Config:"))
  426. {
  427. ScopedPointer <Project::ConfigFlag> config (new Project::ConfigFlag());
  428. config->sourceModuleID = getID();
  429. config->symbol = line.fromFirstOccurrenceOf (":", false, false).trim();
  430. if (config->symbol.length() > 2)
  431. {
  432. ++i;
  433. while (! (lines[i].contains ("*/") || lines[i].contains ("@see")))
  434. {
  435. if (lines[i].trim().isNotEmpty())
  436. config->description = config->description.trim() + " " + lines[i].trim();
  437. ++i;
  438. }
  439. config->description = config->description.upToFirstOccurrenceOf ("*/", false, false);
  440. config->value.referTo (project.getConfigFlag (config->symbol));
  441. flags.add (config.release());
  442. }
  443. }
  444. }
  445. }
  446. //==============================================================================
  447. bool LibraryModule::fileTargetMatches (ProjectExporter& exporter, const String& target)
  448. {
  449. if (target.startsWithChar ('!'))
  450. return ! fileTargetMatches (exporter, target.substring (1).trim());
  451. if (target == "xcode") return exporter.isXcode();
  452. if (target == "msvc") return exporter.isVisualStudio();
  453. if (target == "linux") return exporter.isLinux();
  454. return true;
  455. }
  456. void LibraryModule::findWildcardMatches (const File& localModuleFolder, const String& wildcardPath, Array<File>& result) const
  457. {
  458. String path (wildcardPath.upToLastOccurrenceOf ("/", false, false));
  459. String wildCard (wildcardPath.fromLastOccurrenceOf ("/", false, false));
  460. Array<File> tempList;
  461. FileSorter sorter;
  462. DirectoryIterator iter (localModuleFolder.getChildFile (path), false, wildCard);
  463. bool isHiddenFile;
  464. while (iter.next (nullptr, &isHiddenFile, nullptr, nullptr, nullptr, nullptr))
  465. if (! isHiddenFile)
  466. tempList.addSorted (sorter, iter.getFile());
  467. result.addArray (tempList);
  468. }
  469. void LibraryModule::findAndAddCompiledCode (ProjectExporter& exporter, ProjectSaver& projectSaver,
  470. const File& localModuleFolder, Array<File>& result) const
  471. {
  472. const var compileArray (moduleInfo ["compile"]); // careful to keep this alive while the array is in use!
  473. const Array<var>* const files = compileArray.getArray();
  474. if (files != nullptr)
  475. {
  476. for (int i = 0; i < files->size(); ++i)
  477. {
  478. const var& file = files->getReference(i);
  479. const String filename (file ["file"].toString());
  480. if (filename.isNotEmpty()
  481. && fileTargetMatches (exporter, file ["target"].toString()))
  482. {
  483. const File compiledFile (localModuleFolder.getChildFile (filename));
  484. result.add (compiledFile);
  485. Project::Item item (projectSaver.addFileToGeneratedGroup (compiledFile));
  486. if (file ["warnings"].toString().equalsIgnoreCase ("disabled"))
  487. item.getShouldInhibitWarningsValue() = true;
  488. if (file ["stdcall"])
  489. item.getShouldUseStdCallValue() = true;
  490. }
  491. }
  492. }
  493. }
  494. static void addFileWithGroups (Project::Item& group, const RelativePath& file, const String& path)
  495. {
  496. const int slash = path.indexOfChar (File::separator);
  497. if (slash >= 0)
  498. {
  499. const String topLevelGroup (path.substring (0, slash));
  500. const String remainingPath (path.substring (slash + 1));
  501. Project::Item newGroup (group.getOrCreateSubGroup (topLevelGroup));
  502. addFileWithGroups (newGroup, file, remainingPath);
  503. }
  504. else
  505. {
  506. if (! group.containsChildForFile (file))
  507. group.addRelativeFile (file, -1, false);
  508. }
  509. }
  510. void LibraryModule::addBrowsableCode (ProjectExporter& exporter, const Array<File>& compiled, const File& localModuleFolder) const
  511. {
  512. if (sourceFiles.size() == 0)
  513. {
  514. const var filesArray (moduleInfo ["browse"]);
  515. const Array<var>* const files = filesArray.getArray();
  516. for (int i = 0; i < files->size(); ++i)
  517. findWildcardMatches (localModuleFolder, files->getReference(i), sourceFiles);
  518. }
  519. Project::Item sourceGroup (Project::Item::createGroup (exporter.getProject(), getID(), "__mainsourcegroup" + getID()));
  520. const RelativePath moduleFromProject (getModuleOrLocalCopyRelativeToProject (exporter, localModuleFolder));
  521. for (int i = 0; i < sourceFiles.size(); ++i)
  522. {
  523. const String pathWithinModule (sourceFiles.getReference(i).getRelativePathFrom (localModuleFolder));
  524. // (Note: in exporters like MSVC we have to avoid adding the same file twice, even if one of those instances
  525. // is flagged as being excluded from the build, because this overrides the other and it fails to compile)
  526. if (exporter.canCopeWithDuplicateFiles() || ! compiled.contains (sourceFiles.getReference(i)))
  527. addFileWithGroups (sourceGroup,
  528. moduleFromProject.getChildFile (pathWithinModule),
  529. pathWithinModule);
  530. }
  531. sourceGroup.addFile (localModuleFolder.getChildFile (moduleFile.getRelativePathFrom (moduleFolder)), -1, false);
  532. sourceGroup.addFile (getInclude (localModuleFolder), -1, false);
  533. exporter.getModulesGroup().state.addChild (sourceGroup.state.createCopy(), -1, nullptr);
  534. }