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.

542 lines
18KB

  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. //==============================================================================
  23. static String removeEnclosed (const String& input, const String& start, const String& end)
  24. {
  25. auto startIndex = input.indexOf (start);
  26. auto endIndex = input.indexOf (end) + end.length();
  27. if (startIndex != -1 && endIndex != -1)
  28. return input.replaceSection (startIndex, endIndex - startIndex, {});
  29. return input;
  30. }
  31. static void ensureSingleNewLineAfterIncludes (StringArray& lines)
  32. {
  33. int lastIncludeIndex = -1;
  34. for (int i = 0; i < lines.size(); ++i)
  35. {
  36. if (lines[i].contains ("#include"))
  37. lastIncludeIndex = i;
  38. }
  39. if (lastIncludeIndex != -1)
  40. {
  41. auto index = lastIncludeIndex;
  42. int numNewLines = 0;
  43. while (++index < lines.size() && lines[index].isEmpty())
  44. ++numNewLines;
  45. if (numNewLines > 1)
  46. lines.removeRange (lastIncludeIndex + 1, numNewLines - 1);
  47. }
  48. }
  49. static String ensureCorrectWhitespace (const String& input)
  50. {
  51. auto lines = StringArray::fromLines (input);
  52. ensureSingleNewLineAfterIncludes (lines);
  53. return joinLinesIntoSourceFile (lines);
  54. }
  55. static bool isJUCEExample (const File& pipFile)
  56. {
  57. int numLinesToTest = 10; // license should be at the top of the file so no need to
  58. // check all lines
  59. for (auto line : StringArray::fromLines (pipFile.loadFileAsString()))
  60. {
  61. if (line.contains ("This file is part of the JUCE examples."))
  62. return true;
  63. --numLinesToTest;
  64. }
  65. return false;
  66. }
  67. static bool isValidExporterName (const String& exporterName)
  68. {
  69. return ProjectExporter::getExporterValueTreeNames().contains (exporterName, true);
  70. }
  71. //==============================================================================
  72. PIPGenerator::PIPGenerator (const File& pip, const File& output)
  73. : pipFile (pip),
  74. metadata (parsePIPMetadata())
  75. {
  76. if (output != File())
  77. {
  78. outputDirectory = output;
  79. isTemp = false;
  80. }
  81. else
  82. {
  83. outputDirectory = File::getSpecialLocation (File::SpecialLocationType::tempDirectory).getChildFile ("PIPs");
  84. isTemp = true;
  85. }
  86. outputDirectory = outputDirectory.getChildFile (metadata[Ids::name].toString());
  87. useLocalCopy = metadata[Ids::useLocalCopy].toString().isNotEmpty();
  88. }
  89. //==============================================================================
  90. Result PIPGenerator::createJucerFile()
  91. {
  92. ValueTree root (Ids::JUCERPROJECT);
  93. auto result = setProjectSettings (root);
  94. if (result != Result::ok())
  95. return result;
  96. addModules (root);
  97. addExporters (root);
  98. createFiles (root);
  99. if (! metadata[Ids::moduleFlags].toString().isEmpty())
  100. setModuleFlags (root);
  101. auto outputFile = outputDirectory.getChildFile (metadata[Ids::name].toString() + ".jucer");
  102. ScopedPointer<XmlElement> xml = root.createXml();
  103. if (xml->writeToFile (outputFile, {}))
  104. return Result::ok();
  105. else
  106. return Result::fail ("Failed to create .jucer file in " + outputDirectory.getFullPathName()+ ".");
  107. }
  108. bool PIPGenerator::createMainCpp()
  109. {
  110. auto outputFile = outputDirectory.getChildFile ("Source").getChildFile ("Main.cpp");
  111. if (! outputFile.existsAsFile() && (outputFile.create() != Result::ok()))
  112. return false;
  113. outputFile.replaceWithText (getMainFileTextForType());
  114. return true;
  115. }
  116. //==============================================================================
  117. var PIPGenerator::parsePIPMetadata (const StringArray& lines)
  118. {
  119. auto* o = new DynamicObject();
  120. var result (o);
  121. for (auto& line : lines)
  122. {
  123. line = trimCommentCharsFromStartOfLine (line);
  124. auto colon = line.indexOfChar (':');
  125. if (colon >= 0)
  126. {
  127. auto key = line.substring (0, colon).trim();
  128. auto value = line.substring (colon + 1).trim();
  129. o->setProperty (key, value);
  130. }
  131. }
  132. return result;
  133. }
  134. static String parseMetadataItem (const StringArray& lines, int& index)
  135. {
  136. String result = lines[index++];
  137. while (index < lines.size())
  138. {
  139. auto continuationLine = trimCommentCharsFromStartOfLine (lines[index]);
  140. if (continuationLine.indexOfChar (':') != -1 || continuationLine.startsWith ("END_JUCE_PIP_METADATA"))
  141. break;
  142. result += continuationLine;
  143. ++index;
  144. }
  145. return result;
  146. }
  147. var PIPGenerator::parsePIPMetadata()
  148. {
  149. StringArray lines;
  150. pipFile.readLines (lines);
  151. for (int i = 0; i < lines.size(); ++i)
  152. {
  153. auto trimmedLine = trimCommentCharsFromStartOfLine (lines[i]);
  154. if (trimmedLine.startsWith ("BEGIN_JUCE_PIP_METADATA"))
  155. {
  156. StringArray desc;
  157. auto j = i + 1;
  158. while (j < lines.size())
  159. {
  160. if (trimCommentCharsFromStartOfLine (lines[j]).startsWith ("END_JUCE_PIP_METADATA"))
  161. return parsePIPMetadata (desc);
  162. desc.add (parseMetadataItem (lines, j));
  163. }
  164. }
  165. }
  166. return {};
  167. }
  168. //==============================================================================
  169. void PIPGenerator::addFileToTree (ValueTree& groupTree, const String& name, bool compile, const String& path)
  170. {
  171. ValueTree file (Ids::FILE);
  172. file.setProperty (Ids::ID, createAlphaNumericUID(), nullptr);
  173. file.setProperty (Ids::name, name, nullptr);
  174. file.setProperty (Ids::compile, compile, nullptr);
  175. file.setProperty (Ids::resource, 0, nullptr);
  176. file.setProperty (Ids::file, path, nullptr);
  177. groupTree.addChild (file, -1, nullptr);
  178. }
  179. void PIPGenerator::createFiles (ValueTree& jucerTree)
  180. {
  181. auto sourceDir = outputDirectory.getChildFile ("Source");
  182. if (! sourceDir.exists())
  183. sourceDir.createDirectory();
  184. if (useLocalCopy)
  185. pipFile.copyFileTo (sourceDir.getChildFile (pipFile.getFileName()));
  186. ValueTree mainGroup (Ids::MAINGROUP);
  187. mainGroup.setProperty (Ids::ID, createAlphaNumericUID(), nullptr);
  188. mainGroup.setProperty (Ids::name, metadata[Ids::name], nullptr);
  189. ValueTree group (Ids::GROUP);
  190. group.setProperty (Ids::ID, createGUID (sourceDir.getFullPathName() + "_guidpathsaltxhsdf"), nullptr);
  191. group.setProperty (Ids::name, "Source", nullptr);
  192. addFileToTree (group, "Main.cpp", true, "Source/Main.cpp");
  193. addFileToTree (group, pipFile.getFileName(), false, useLocalCopy ? "Source/" + pipFile.getFileName()
  194. : pipFile.getFullPathName());
  195. mainGroup.addChild (group, -1, nullptr);
  196. if (useLocalCopy)
  197. {
  198. auto relativeFiles = replaceRelativeIncludesAndGetFilesToMove();
  199. if (relativeFiles.size() > 0)
  200. {
  201. ValueTree assets (Ids::GROUP);
  202. assets.setProperty (Ids::ID, createAlphaNumericUID(), nullptr);
  203. assets.setProperty (Ids::name, "Assets", nullptr);
  204. for (auto& f : relativeFiles)
  205. if (copyRelativeFileToLocalSourceDirectory (f))
  206. addFileToTree (assets, f.getFileName(), f.getFileExtension() == ".cpp", "Source/" + f.getFileName());
  207. mainGroup.addChild (assets, -1, nullptr);
  208. }
  209. }
  210. jucerTree.addChild (mainGroup, 0, nullptr);
  211. }
  212. ValueTree PIPGenerator::createModulePathChild (const String& moduleID)
  213. {
  214. ValueTree modulePath (Ids::MODULEPATH);
  215. modulePath.setProperty (Ids::ID, moduleID, nullptr);
  216. modulePath.setProperty (Ids::path, {}, nullptr);
  217. return modulePath;
  218. }
  219. ValueTree PIPGenerator::createBuildConfigChild (bool isDebug)
  220. {
  221. ValueTree child (Ids::CONFIGURATIONS);
  222. child.setProperty (Ids::name, isDebug ? "Debug" : "Release", nullptr);
  223. child.setProperty (Ids::isDebug, isDebug ? 1 : 0, nullptr);
  224. child.setProperty (Ids::optimisation, isDebug ? 1 : 3, nullptr);
  225. child.setProperty (Ids::targetName, metadata[Ids::name], nullptr);
  226. return child;
  227. }
  228. ValueTree PIPGenerator::createExporterChild (const String& exporterName)
  229. {
  230. ValueTree exporter (exporterName);
  231. exporter.setProperty (Ids::targetFolder, "Builds/" + ProjectExporter::getTargetFolderForExporter (exporterName), nullptr);
  232. {
  233. ValueTree configs (Ids::CONFIGURATIONS);
  234. configs.addChild (createBuildConfigChild (true), -1, nullptr);
  235. configs.addChild (createBuildConfigChild (false), -1, nullptr);
  236. exporter.addChild (configs, -1, nullptr);
  237. }
  238. {
  239. ValueTree modulePaths (Ids::MODULEPATHS);
  240. auto modules = StringArray::fromTokens (metadata[Ids::dependencies_].toString(), ",", {});
  241. for (auto m : modules)
  242. {
  243. m = m.trim();
  244. if (isJUCEModule (m))
  245. modulePaths.addChild (createModulePathChild (m), -1, nullptr);
  246. }
  247. exporter.addChild (modulePaths, -1, nullptr);
  248. }
  249. return exporter;
  250. }
  251. ValueTree PIPGenerator::createModuleChild (const String& moduleID)
  252. {
  253. ValueTree module (Ids::MODULE);
  254. module.setProperty (Ids::ID, moduleID, nullptr);
  255. module.setProperty (Ids::showAllCode, 1, nullptr);
  256. module.setProperty (Ids::useLocalCopy, 0, nullptr);
  257. module.setProperty (Ids::useGlobalPath, 1, nullptr);
  258. return module;
  259. }
  260. void PIPGenerator::addExporters (ValueTree& jucerTree)
  261. {
  262. ValueTree exportersTree (Ids::EXPORTFORMATS);
  263. auto exporters = StringArray::fromTokens (metadata[Ids::exporters].toString(), ",", {});
  264. for (auto& e : exporters)
  265. {
  266. e = e.trim().toUpperCase();
  267. if (isValidExporterName (e))
  268. exportersTree.addChild (createExporterChild (e), -1, nullptr);
  269. }
  270. jucerTree.addChild (exportersTree, -1, nullptr);
  271. }
  272. void PIPGenerator::addModules (ValueTree& jucerTree)
  273. {
  274. ValueTree modulesTree (Ids::MODULES);
  275. auto modules = StringArray::fromTokens (metadata[Ids::dependencies_].toString(), ",", {});
  276. modules.trim();
  277. auto projectType = metadata[Ids::type].toString();
  278. if (projectType == "Console")
  279. modules.mergeArray (getModulesRequiredForConsole());
  280. else if (projectType == "Component")
  281. modules.mergeArray (getModulesRequiredForComponent());
  282. else if (projectType == "AudioProcessor")
  283. modules.mergeArray (getModulesRequiredForAudioProcessor());
  284. for (auto& m : modules)
  285. {
  286. m = m.trim();
  287. if (isJUCEModule (m))
  288. modulesTree.addChild (createModuleChild (m), -1, nullptr);
  289. }
  290. jucerTree.addChild (modulesTree, -1, nullptr);
  291. }
  292. Result PIPGenerator::setProjectSettings (ValueTree& jucerTree)
  293. {
  294. jucerTree.setProperty (Ids::name, metadata[Ids::name], nullptr);
  295. jucerTree.setProperty (Ids::companyName, metadata[Ids::vendor], nullptr);
  296. jucerTree.setProperty (Ids::version, metadata[Ids::version], nullptr);
  297. jucerTree.setProperty (Ids::userNotes, metadata[Ids::description], nullptr);
  298. jucerTree.setProperty (Ids::companyWebsite, metadata[Ids::website], nullptr);
  299. auto defines = metadata[Ids::defines].toString();
  300. if (useLocalCopy && isJUCEExample (pipFile))
  301. {
  302. auto juceDir = getAppSettings().getStoredPath (Ids::jucePath).toString();
  303. if (juceDir.isNotEmpty() && isValidJUCEExamplesDirectory (File (juceDir).getChildFile ("examples")))
  304. {
  305. defines += ((defines.isEmpty() ? "" : " ") + String ("PIP_JUCE_EXAMPLES_DIRECTORY=")
  306. + Base64::toBase64 (File (juceDir).getChildFile ("examples").getFullPathName()));
  307. }
  308. else
  309. {
  310. return Result::fail (String ("Invalid JUCE path. Set path to JUCE via ") +
  311. (TargetOS::getThisOS() == TargetOS::osx ? "\"Projucer->Global Paths...\""
  312. : "\"File->Global Paths...\"")
  313. + " menu item.");
  314. }
  315. }
  316. jucerTree.setProperty (Ids::defines, defines, nullptr);
  317. auto type = metadata[Ids::type].toString();
  318. if (type == "Console")
  319. {
  320. jucerTree.setProperty (Ids::projectType, "consoleapp", nullptr);
  321. }
  322. else if (type == "Component")
  323. {
  324. jucerTree.setProperty (Ids::projectType, "guiapp", nullptr);
  325. }
  326. else if (type == "AudioProcessor")
  327. {
  328. jucerTree.setProperty (Ids::projectType, "audioplug", nullptr);
  329. jucerTree.setProperty (Ids::pluginManufacturer, metadata[Ids::vendor], nullptr);
  330. jucerTree.setProperty (Ids::buildVST, true, nullptr);
  331. jucerTree.setProperty (Ids::buildVST3, false, nullptr);
  332. jucerTree.setProperty (Ids::buildAU, true, nullptr);
  333. jucerTree.setProperty (Ids::buildAUv3, false, nullptr);
  334. jucerTree.setProperty (Ids::buildRTAS, false, nullptr);
  335. jucerTree.setProperty (Ids::buildAAX, false, nullptr);
  336. jucerTree.setProperty (Ids::buildStandalone, true, nullptr);
  337. }
  338. return Result::ok();
  339. }
  340. void PIPGenerator::setModuleFlags (ValueTree& jucerTree)
  341. {
  342. ValueTree options ("JUCEOPTIONS");
  343. for (auto& option : StringArray::fromTokens (metadata[Ids::moduleFlags].toString(), ",", {}))
  344. {
  345. auto name = option.upToFirstOccurrenceOf ("=", false, true).trim();
  346. auto value = option.fromFirstOccurrenceOf ("=", false, true).trim();
  347. options.setProperty (name, (value == "1" ? 1 : 0), nullptr);
  348. }
  349. jucerTree.addChild (options, -1, nullptr);
  350. }
  351. String PIPGenerator::getMainFileTextForType()
  352. {
  353. String mainTemplate (BinaryData::jucer_PIPMain_cpp);
  354. mainTemplate = mainTemplate.replace ("%%filename%%", useLocalCopy ? pipFile.getFileName()
  355. : pipFile.getFullPathName());
  356. auto type = metadata[Ids::type].toString();
  357. if (type == "Console")
  358. {
  359. mainTemplate = removeEnclosed (mainTemplate, "%%component_begin%%", "%%component_end%%");
  360. mainTemplate = removeEnclosed (mainTemplate, "%%audioprocessor_begin%%", "%%audioprocessor_end%%");
  361. mainTemplate = mainTemplate.replace ("%%console_begin%%", {}).replace ("%%console_end%%", {});
  362. return ensureCorrectWhitespace (mainTemplate);
  363. }
  364. else if (type == "Component")
  365. {
  366. mainTemplate = removeEnclosed (mainTemplate, "%%audioprocessor_begin%%", "%%audioprocessor_end%%");
  367. mainTemplate = removeEnclosed (mainTemplate, "%%console_begin%%", "%%console_end%%");
  368. mainTemplate = mainTemplate.replace ("%%component_begin%%", {}).replace ("%%component_end%%", {});
  369. mainTemplate = mainTemplate.replace ("%%project_name%%", metadata[Ids::name].toString());
  370. mainTemplate = mainTemplate.replace ("%%project_version%%", metadata[Ids::version].toString());
  371. return ensureCorrectWhitespace (mainTemplate.replace ("%%startup%%", "mainWindow = new MainWindow (" + metadata[Ids::name].toString().quoted()
  372. + ", new " + metadata[Ids::mainClass].toString() + "());")
  373. .replace ("%%shutdown%%", "mainWindow = nullptr;"));
  374. }
  375. else if (type == "AudioProcessor")
  376. {
  377. mainTemplate = removeEnclosed (mainTemplate, "%%component_begin%%", "%%component_end%%");
  378. mainTemplate = removeEnclosed (mainTemplate, "%%console_begin%%", "%%console_end%%");
  379. mainTemplate = mainTemplate.replace ("%%audioprocessor_begin%%", {}).replace ("%%audioprocessor_end%%", {});
  380. return ensureCorrectWhitespace (mainTemplate.replace ("%%class_name%%", metadata[Ids::mainClass].toString()));
  381. }
  382. return {};
  383. }
  384. //==============================================================================
  385. Array<File> PIPGenerator::replaceRelativeIncludesAndGetFilesToMove()
  386. {
  387. StringArray lines;
  388. pipFile.readLines (lines);
  389. Array<File> files;
  390. for (auto& line : lines)
  391. {
  392. if (line.contains ("#include") && ! line.contains ("JuceLibraryCode"))
  393. {
  394. auto path = line.fromFirstOccurrenceOf ("#include", false, false);
  395. path = path.removeCharacters ("\"").trim();
  396. if (path.startsWith ("<") && path.endsWith (">"))
  397. continue;
  398. auto file = pipFile.getParentDirectory().getChildFile (path);
  399. files.add (file);
  400. line = line.replace (path, file.getFileName());
  401. }
  402. }
  403. outputDirectory.getChildFile ("Source")
  404. .getChildFile (pipFile.getFileName())
  405. .replaceWithText (joinLinesIntoSourceFile (lines));
  406. return files;
  407. }
  408. bool PIPGenerator::copyRelativeFileToLocalSourceDirectory (const File& fileToCopy) const noexcept
  409. {
  410. return fileToCopy.copyFileTo (outputDirectory.getChildFile ("Source")
  411. .getChildFile (fileToCopy.getFileName()));
  412. }