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.

626 lines
21KB

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