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.

559 lines
19KB

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