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.

516 lines
18KB

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