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.

472 lines
16KB

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