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.

569 lines
20KB

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