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.

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