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.

581 lines
21KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2017 - ROLI Ltd.
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 5 End-User License
  8. Agreement and JUCE 5 Privacy Policy (both updated and effective as of the
  9. 27th April 2017).
  10. End User License Agreement: www.juce.com/juce-5-licence
  11. Privacy Policy: www.juce.com/juce-5-privacy-policy
  12. Or: You may also use this code under the terms of the GPL v3 (see
  13. www.gnu.org/licenses).
  14. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  15. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  16. DISCLAIMED.
  17. ==============================================================================
  18. */
  19. #include "../../Application/jucer_Headers.h"
  20. #include "../../ProjectSaving/jucer_ProjectExporter.h"
  21. #include "jucer_PIPGenerator.h"
  22. #include "../../Project/jucer_Module.h"
  23. //==============================================================================
  24. static String removeEnclosed (const String& input, const String& start, const String& end)
  25. {
  26. auto startIndex = input.indexOf (start);
  27. auto endIndex = input.indexOf (end) + end.length();
  28. if (startIndex != -1 && endIndex != -1)
  29. return input.replaceSection (startIndex, endIndex - startIndex, {});
  30. return input;
  31. }
  32. static void ensureSingleNewLineAfterIncludes (StringArray& lines)
  33. {
  34. int lastIncludeIndex = -1;
  35. for (int i = 0; i < lines.size(); ++i)
  36. {
  37. if (lines[i].contains ("#include"))
  38. lastIncludeIndex = i;
  39. }
  40. if (lastIncludeIndex != -1)
  41. {
  42. auto index = lastIncludeIndex;
  43. int numNewLines = 0;
  44. while (++index < lines.size() && lines[index].isEmpty())
  45. ++numNewLines;
  46. if (numNewLines > 1)
  47. lines.removeRange (lastIncludeIndex + 1, numNewLines - 1);
  48. }
  49. }
  50. static String ensureCorrectWhitespace (StringRef input)
  51. {
  52. auto lines = StringArray::fromLines (input);
  53. ensureSingleNewLineAfterIncludes (lines);
  54. return joinLinesIntoSourceFile (lines);
  55. }
  56. static bool isJUCEExample (const File& pipFile)
  57. {
  58. int numLinesToTest = 10; // license should be at the top of the file so no need to
  59. // check all lines
  60. for (auto line : StringArray::fromLines (pipFile.loadFileAsString()))
  61. {
  62. if (line.contains ("This file is part of the JUCE examples."))
  63. return true;
  64. --numLinesToTest;
  65. }
  66. return false;
  67. }
  68. static bool isValidExporterName (StringRef exporterName)
  69. {
  70. return ProjectExporter::getExporterValueTreeNames().contains (exporterName, true);
  71. }
  72. static bool exporterRequiresExampleAssets (const String& exporterName, const String& projectName)
  73. {
  74. return (exporterName == "XCODE_IPHONE" || exporterName == "ANDROIDSTUDIO")
  75. || (exporterName == "XCODE_MAC" && projectName == "AUv3SynthPlugin");
  76. }
  77. //==============================================================================
  78. PIPGenerator::PIPGenerator (const File& pip, const File& output, const File& jucePath, const File& userPath)
  79. : pipFile (pip),
  80. juceModulesPath (jucePath),
  81. userModulesPath (userPath),
  82. metadata (parseJUCEHeaderMetadata (pipFile))
  83. {
  84. if (output != File())
  85. {
  86. outputDirectory = output;
  87. isTemp = false;
  88. }
  89. else
  90. {
  91. outputDirectory = File::getSpecialLocation (File::SpecialLocationType::tempDirectory).getChildFile ("PIPs");
  92. isTemp = true;
  93. }
  94. auto isClipboard = (pip.getParentDirectory().getFileName() == "Clipboard"
  95. && pip.getParentDirectory().getParentDirectory().getFileName() == "PIPs");
  96. outputDirectory = outputDirectory.getChildFile (metadata[Ids::name].toString());
  97. useLocalCopy = metadata[Ids::useLocalCopy].toString().isNotEmpty() || isClipboard;
  98. if (userModulesPath != File())
  99. {
  100. availableUserModules.reset (new AvailableModuleList());
  101. availableUserModules->scanPaths ({ userModulesPath });
  102. }
  103. }
  104. //==============================================================================
  105. Result PIPGenerator::createJucerFile()
  106. {
  107. ValueTree root (Ids::JUCERPROJECT);
  108. auto result = setProjectSettings (root);
  109. if (result != Result::ok())
  110. return result;
  111. addModules (root);
  112. addExporters (root);
  113. createFiles (root);
  114. setModuleFlags (root);
  115. auto outputFile = outputDirectory.getChildFile (metadata[Ids::name].toString() + ".jucer");
  116. std::unique_ptr<XmlElement> xml (root.createXml());
  117. if (xml->writeToFile (outputFile, {}))
  118. return Result::ok();
  119. return Result::fail ("Failed to create .jucer file in " + outputDirectory.getFullPathName());
  120. }
  121. Result PIPGenerator::createMainCpp()
  122. {
  123. auto outputFile = outputDirectory.getChildFile ("Source").getChildFile ("Main.cpp");
  124. if (! outputFile.existsAsFile() && (outputFile.create() != Result::ok()))
  125. return Result::fail ("Failed to create Main.cpp - " + outputFile.getFullPathName());
  126. outputFile.replaceWithText (getMainFileTextForType());
  127. return Result::ok();
  128. }
  129. //==============================================================================
  130. void PIPGenerator::addFileToTree (ValueTree& groupTree, const String& name, bool compile, const String& path)
  131. {
  132. ValueTree file (Ids::FILE);
  133. file.setProperty (Ids::ID, createAlphaNumericUID(), nullptr);
  134. file.setProperty (Ids::name, name, nullptr);
  135. file.setProperty (Ids::compile, compile, nullptr);
  136. file.setProperty (Ids::resource, 0, nullptr);
  137. file.setProperty (Ids::file, path, nullptr);
  138. groupTree.addChild (file, -1, nullptr);
  139. }
  140. void PIPGenerator::createFiles (ValueTree& jucerTree)
  141. {
  142. auto sourceDir = outputDirectory.getChildFile ("Source");
  143. if (! sourceDir.exists())
  144. sourceDir.createDirectory();
  145. if (useLocalCopy)
  146. pipFile.copyFileTo (sourceDir.getChildFile (pipFile.getFileName()));
  147. ValueTree mainGroup (Ids::MAINGROUP);
  148. mainGroup.setProperty (Ids::ID, createAlphaNumericUID(), nullptr);
  149. mainGroup.setProperty (Ids::name, metadata[Ids::name], nullptr);
  150. ValueTree group (Ids::GROUP);
  151. group.setProperty (Ids::ID, createGUID (sourceDir.getFullPathName() + "_guidpathsaltxhsdf"), nullptr);
  152. group.setProperty (Ids::name, "Source", nullptr);
  153. addFileToTree (group, "Main.cpp", true, "Source/Main.cpp");
  154. addFileToTree (group, pipFile.getFileName(), false, useLocalCopy ? "Source/" + pipFile.getFileName()
  155. : pipFile.getFullPathName());
  156. mainGroup.addChild (group, -1, nullptr);
  157. if (useLocalCopy)
  158. {
  159. auto relativeFiles = replaceRelativeIncludesAndGetFilesToMove();
  160. if (relativeFiles.size() > 0)
  161. {
  162. ValueTree assets (Ids::GROUP);
  163. assets.setProperty (Ids::ID, createAlphaNumericUID(), nullptr);
  164. assets.setProperty (Ids::name, "Assets", nullptr);
  165. for (auto& f : relativeFiles)
  166. if (copyRelativeFileToLocalSourceDirectory (f))
  167. addFileToTree (assets, f.getFileName(), f.getFileExtension() == ".cpp", "Source/" + f.getFileName());
  168. mainGroup.addChild (assets, -1, nullptr);
  169. }
  170. }
  171. jucerTree.addChild (mainGroup, 0, nullptr);
  172. }
  173. ValueTree PIPGenerator::createModulePathChild (const String& moduleID)
  174. {
  175. ValueTree modulePath (Ids::MODULEPATH);
  176. modulePath.setProperty (Ids::ID, moduleID, nullptr);
  177. modulePath.setProperty (Ids::path, getPathForModule (moduleID), nullptr);
  178. return modulePath;
  179. }
  180. ValueTree PIPGenerator::createBuildConfigChild (bool isDebug)
  181. {
  182. ValueTree child (Ids::CONFIGURATIONS);
  183. child.setProperty (Ids::name, isDebug ? "Debug" : "Release", nullptr);
  184. child.setProperty (Ids::isDebug, isDebug ? 1 : 0, nullptr);
  185. child.setProperty (Ids::optimisation, isDebug ? 1 : 3, nullptr);
  186. child.setProperty (Ids::targetName, metadata[Ids::name], nullptr);
  187. return child;
  188. }
  189. ValueTree PIPGenerator::createExporterChild (const String& exporterName)
  190. {
  191. ValueTree exporter (exporterName);
  192. exporter.setProperty (Ids::targetFolder, "Builds/" + ProjectExporter::getTargetFolderForExporter (exporterName), nullptr);
  193. if (isJUCEExample (pipFile) && exporterRequiresExampleAssets (exporterName, metadata[Ids::name]))
  194. {
  195. auto examplesDir = getExamplesDirectory();
  196. if (examplesDir != File())
  197. {
  198. auto assetsDirectoryPath = examplesDir.getChildFile ("Assets").getFullPathName();
  199. exporter.setProperty (exporterName == "ANDROIDSTUDIO" ? Ids::androidExtraAssetsFolder
  200. : Ids::customXcodeResourceFolders,
  201. assetsDirectoryPath, nullptr);
  202. }
  203. else
  204. {
  205. // invalid JUCE path
  206. jassertfalse;
  207. }
  208. }
  209. {
  210. ValueTree configs (Ids::CONFIGURATIONS);
  211. configs.addChild (createBuildConfigChild (true), -1, nullptr);
  212. configs.addChild (createBuildConfigChild (false), -1, nullptr);
  213. exporter.addChild (configs, -1, nullptr);
  214. }
  215. {
  216. ValueTree modulePaths (Ids::MODULEPATHS);
  217. auto modules = StringArray::fromTokens (metadata[Ids::dependencies_].toString(), ",", {});
  218. for (auto m : modules)
  219. modulePaths.addChild (createModulePathChild (m.trim()), -1, nullptr);
  220. exporter.addChild (modulePaths, -1, nullptr);
  221. }
  222. return exporter;
  223. }
  224. ValueTree PIPGenerator::createModuleChild (const String& moduleID)
  225. {
  226. ValueTree module (Ids::MODULE);
  227. module.setProperty (Ids::ID, moduleID, nullptr);
  228. module.setProperty (Ids::showAllCode, 1, nullptr);
  229. module.setProperty (Ids::useLocalCopy, 0, nullptr);
  230. module.setProperty (Ids::useGlobalPath, (getPathForModule (moduleID).isEmpty() ? 1 : 0), nullptr);
  231. return module;
  232. }
  233. void PIPGenerator::addExporters (ValueTree& jucerTree)
  234. {
  235. ValueTree exportersTree (Ids::EXPORTFORMATS);
  236. auto exporters = StringArray::fromTokens (metadata[Ids::exporters].toString(), ",", {});
  237. for (auto& e : exporters)
  238. {
  239. e = e.trim().toUpperCase();
  240. if (isValidExporterName (e))
  241. exportersTree.addChild (createExporterChild (e), -1, nullptr);
  242. }
  243. jucerTree.addChild (exportersTree, -1, nullptr);
  244. }
  245. void PIPGenerator::addModules (ValueTree& jucerTree)
  246. {
  247. ValueTree modulesTree (Ids::MODULES);
  248. auto modules = StringArray::fromTokens (metadata[Ids::dependencies_].toString(), ",", {});
  249. modules.trim();
  250. auto projectType = metadata[Ids::type].toString();
  251. if (projectType == "Console")
  252. modules.mergeArray (getModulesRequiredForConsole());
  253. else if (projectType == "Component")
  254. modules.mergeArray (getModulesRequiredForComponent());
  255. else if (projectType == "AudioProcessor")
  256. modules.mergeArray (getModulesRequiredForAudioProcessor());
  257. for (auto& m : modules)
  258. modulesTree.addChild (createModuleChild (m.trim()), -1, nullptr);
  259. jucerTree.addChild (modulesTree, -1, nullptr);
  260. }
  261. Result PIPGenerator::setProjectSettings (ValueTree& jucerTree)
  262. {
  263. jucerTree.setProperty (Ids::name, metadata[Ids::name], nullptr);
  264. jucerTree.setProperty (Ids::companyName, metadata[Ids::vendor], nullptr);
  265. jucerTree.setProperty (Ids::version, metadata[Ids::version], nullptr);
  266. jucerTree.setProperty (Ids::userNotes, metadata[Ids::description], nullptr);
  267. jucerTree.setProperty (Ids::companyWebsite, metadata[Ids::website], nullptr);
  268. auto defines = metadata[Ids::defines].toString();
  269. if (useLocalCopy && isJUCEExample (pipFile))
  270. {
  271. auto examplesDir = getExamplesDirectory();
  272. if (examplesDir != File())
  273. {
  274. defines += ((defines.isEmpty() ? "" : " ") + String ("PIP_JUCE_EXAMPLES_DIRECTORY=")
  275. + Base64::toBase64 (examplesDir.getFullPathName()));
  276. }
  277. else
  278. {
  279. return Result::fail (String ("Invalid JUCE path. Set path to JUCE via ") +
  280. (TargetOS::getThisOS() == TargetOS::osx ? "\"Projucer->Global Paths...\""
  281. : "\"File->Global Paths...\"")
  282. + " menu item.");
  283. }
  284. }
  285. jucerTree.setProperty (Ids::defines, defines, nullptr);
  286. auto type = metadata[Ids::type].toString();
  287. if (type == "Console")
  288. {
  289. jucerTree.setProperty (Ids::projectType, "consoleapp", nullptr);
  290. }
  291. else if (type == "Component")
  292. {
  293. jucerTree.setProperty (Ids::projectType, "guiapp", nullptr);
  294. }
  295. else if (type == "AudioProcessor")
  296. {
  297. jucerTree.setProperty (Ids::projectType, "audioplug", nullptr);
  298. jucerTree.setProperty (Ids::pluginManufacturer, metadata[Ids::vendor], nullptr);
  299. jucerTree.setProperty (Ids::pluginAUIsSandboxSafe, "1", nullptr);
  300. StringArray pluginFormatsToBuild (Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildStandalone.toString());
  301. pluginFormatsToBuild.addArray (getExtraPluginFormatsToBuild());
  302. jucerTree.setProperty (Ids::pluginFormats, pluginFormatsToBuild.joinIntoString (","), nullptr);
  303. if (! getPluginCharacteristics().isEmpty())
  304. jucerTree.setProperty (Ids::pluginCharacteristicsValue, getPluginCharacteristics().joinIntoString (","), nullptr);
  305. }
  306. return Result::ok();
  307. }
  308. void PIPGenerator::setModuleFlags (ValueTree& jucerTree)
  309. {
  310. ValueTree options ("JUCEOPTIONS");
  311. for (auto& option : StringArray::fromTokens (metadata[Ids::moduleFlags].toString(), ",", {}))
  312. {
  313. auto name = option.upToFirstOccurrenceOf ("=", false, true).trim();
  314. auto value = option.fromFirstOccurrenceOf ("=", false, true).trim();
  315. options.setProperty (name, (value == "1" ? 1 : 0), nullptr);
  316. }
  317. if (metadata[Ids::type].toString() == "AudioProcessor"
  318. && options.getPropertyPointer ("JUCE_VST3_CAN_REPLACE_VST2") == nullptr)
  319. options.setProperty ("JUCE_VST3_CAN_REPLACE_VST2", 0, nullptr);
  320. jucerTree.addChild (options, -1, nullptr);
  321. }
  322. String PIPGenerator::getMainFileTextForType()
  323. {
  324. String mainTemplate (BinaryData::jucer_PIPMain_cpp);
  325. mainTemplate = mainTemplate.replace ("%%filename%%", useLocalCopy ? pipFile.getFileName()
  326. : isTemp ? pipFile.getFullPathName()
  327. : RelativePath (pipFile, outputDirectory.getChildFile ("Source"),
  328. RelativePath::unknown).toUnixStyle());
  329. auto type = metadata[Ids::type].toString();
  330. if (type == "Console")
  331. {
  332. mainTemplate = removeEnclosed (mainTemplate, "%%component_begin%%", "%%component_end%%");
  333. mainTemplate = removeEnclosed (mainTemplate, "%%audioprocessor_begin%%", "%%audioprocessor_end%%");
  334. mainTemplate = mainTemplate.replace ("%%console_begin%%", {}).replace ("%%console_end%%", {});
  335. return ensureCorrectWhitespace (mainTemplate);
  336. }
  337. else if (type == "Component")
  338. {
  339. mainTemplate = removeEnclosed (mainTemplate, "%%audioprocessor_begin%%", "%%audioprocessor_end%%");
  340. mainTemplate = removeEnclosed (mainTemplate, "%%console_begin%%", "%%console_end%%");
  341. mainTemplate = mainTemplate.replace ("%%component_begin%%", {}).replace ("%%component_end%%", {});
  342. mainTemplate = mainTemplate.replace ("%%project_name%%", metadata[Ids::name].toString());
  343. mainTemplate = mainTemplate.replace ("%%project_version%%", metadata[Ids::version].toString());
  344. return ensureCorrectWhitespace (mainTemplate.replace ("%%startup%%", "mainWindow.reset (new MainWindow (" + metadata[Ids::name].toString().quoted()
  345. + ", new " + metadata[Ids::mainClass].toString() + "(), *this));")
  346. .replace ("%%shutdown%%", "mainWindow = nullptr;"));
  347. }
  348. else if (type == "AudioProcessor")
  349. {
  350. mainTemplate = removeEnclosed (mainTemplate, "%%component_begin%%", "%%component_end%%");
  351. mainTemplate = removeEnclosed (mainTemplate, "%%console_begin%%", "%%console_end%%");
  352. mainTemplate = mainTemplate.replace ("%%audioprocessor_begin%%", {}).replace ("%%audioprocessor_end%%", {});
  353. return ensureCorrectWhitespace (mainTemplate.replace ("%%class_name%%", metadata[Ids::mainClass].toString()));
  354. }
  355. return {};
  356. }
  357. //==============================================================================
  358. Array<File> PIPGenerator::replaceRelativeIncludesAndGetFilesToMove()
  359. {
  360. StringArray lines;
  361. pipFile.readLines (lines);
  362. Array<File> files;
  363. for (auto& line : lines)
  364. {
  365. if (line.contains ("#include") && ! line.contains ("JuceLibraryCode"))
  366. {
  367. auto path = line.fromFirstOccurrenceOf ("#include", false, false);
  368. path = path.removeCharacters ("\"").trim();
  369. if (path.startsWith ("<") && path.endsWith (">"))
  370. continue;
  371. auto file = pipFile.getParentDirectory().getChildFile (path);
  372. files.add (file);
  373. line = line.replace (path, file.getFileName());
  374. }
  375. }
  376. outputDirectory.getChildFile ("Source")
  377. .getChildFile (pipFile.getFileName())
  378. .replaceWithText (joinLinesIntoSourceFile (lines));
  379. return files;
  380. }
  381. bool PIPGenerator::copyRelativeFileToLocalSourceDirectory (const File& fileToCopy) const noexcept
  382. {
  383. return fileToCopy.copyFileTo (outputDirectory.getChildFile ("Source")
  384. .getChildFile (fileToCopy.getFileName()));
  385. }
  386. StringArray PIPGenerator::getExtraPluginFormatsToBuild() const
  387. {
  388. auto name = metadata[Ids::name].toString();
  389. if (name == "AUv3SynthPlugin" || name == "AudioPluginDemo")
  390. return { Ids::buildAUv3.toString() };
  391. else if (name == "InterAppAudioEffectPlugin")
  392. return { Ids::enableIAA.toString() };
  393. return {};
  394. }
  395. StringArray PIPGenerator::getPluginCharacteristics() const
  396. {
  397. auto name = metadata[Ids::name].toString();
  398. if (name == "AudioPluginDemo")
  399. return { Ids::pluginIsSynth.toString(),
  400. Ids::pluginWantsMidiIn.toString(),
  401. Ids::pluginProducesMidiOut.toString(),
  402. Ids::pluginEditorRequiresKeys.toString() };
  403. else if (name == "AUv3SynthPlugin" || name == "MultiOutSynthPlugin")
  404. return { Ids::pluginIsSynth.toString(),
  405. Ids::pluginWantsMidiIn.toString(),
  406. Ids::pluginIsSynth.toString() };
  407. else if (name == "ArpeggiatorPlugin")
  408. return { Ids::pluginWantsMidiIn.toString(),
  409. Ids::pluginProducesMidiOut.toString(),
  410. Ids::pluginIsMidiEffectPlugin.toString() };
  411. return {};
  412. }
  413. String PIPGenerator::getPathForModule (const String& moduleID) const
  414. {
  415. if (isJUCEModule (moduleID))
  416. {
  417. if (juceModulesPath != File())
  418. {
  419. if (isTemp)
  420. return juceModulesPath.getFullPathName();
  421. return RelativePath (juceModulesPath, outputDirectory, RelativePath::projectFolder).toUnixStyle();
  422. }
  423. }
  424. else if (availableUserModules != nullptr)
  425. {
  426. auto moduleRoot = availableUserModules->getModuleWithID (moduleID).second.getParentDirectory();
  427. if (isTemp)
  428. return moduleRoot.getFullPathName();
  429. return RelativePath (moduleRoot , outputDirectory, RelativePath::projectFolder).toUnixStyle();
  430. }
  431. return {};
  432. }
  433. File PIPGenerator::getExamplesDirectory() const
  434. {
  435. if (juceModulesPath != File())
  436. {
  437. auto examples = juceModulesPath.getSiblingFile ("examples");
  438. if (isValidJUCEExamplesDirectory (examples))
  439. return examples;
  440. }
  441. auto examples = File (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString()).getChildFile ("examples");
  442. if (isValidJUCEExamplesDirectory (examples))
  443. return examples;
  444. return {};
  445. }