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.

560 lines
19KB

  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. for (auto& m : modules)
  244. modulesTree.addChild (createModuleChild (m.trim()), -1, nullptr);
  245. jucerTree.addChild (modulesTree, -1, nullptr);
  246. }
  247. Result PIPGenerator::setProjectSettings (ValueTree& jucerTree)
  248. {
  249. auto setPropertyIfNotEmpty = [&jucerTree] (const Identifier& name, const var& value)
  250. {
  251. if (value != var())
  252. jucerTree.setProperty (name, value, nullptr);
  253. };
  254. setPropertyIfNotEmpty (Ids::name, metadata[Ids::name]);
  255. setPropertyIfNotEmpty (Ids::companyName, metadata[Ids::vendor]);
  256. setPropertyIfNotEmpty (Ids::version, metadata[Ids::version]);
  257. setPropertyIfNotEmpty (Ids::userNotes, metadata[Ids::description]);
  258. setPropertyIfNotEmpty (Ids::companyWebsite, metadata[Ids::website]);
  259. auto defines = metadata[Ids::defines].toString();
  260. if (isJUCEExample (pipFile))
  261. {
  262. auto examplesDir = getExamplesDirectory();
  263. if (examplesDir != File())
  264. {
  265. defines += ((defines.isEmpty() ? "" : " ") + String ("PIP_JUCE_EXAMPLES_DIRECTORY=")
  266. + Base64::toBase64 (examplesDir.getFullPathName()));
  267. }
  268. else
  269. {
  270. return Result::fail (String ("Invalid JUCE path. Set path to JUCE via ") +
  271. (TargetOS::getThisOS() == TargetOS::osx ? "\"Projucer->Global Paths...\""
  272. : "\"File->Global Paths...\"")
  273. + " menu item.");
  274. }
  275. jucerTree.setProperty (Ids::displaySplashScreen, true, nullptr);
  276. }
  277. setPropertyIfNotEmpty (Ids::defines, defines);
  278. auto type = metadata[Ids::type].toString();
  279. if (type == "Console")
  280. {
  281. jucerTree.setProperty (Ids::projectType, build_tools::ProjectType_ConsoleApp::getTypeName(), nullptr);
  282. }
  283. else if (type == "Component")
  284. {
  285. jucerTree.setProperty (Ids::projectType, build_tools::ProjectType_GUIApp::getTypeName(), nullptr);
  286. }
  287. else if (type == "AudioProcessor")
  288. {
  289. jucerTree.setProperty (Ids::projectType, build_tools::ProjectType_AudioPlugin::getTypeName(), nullptr);
  290. jucerTree.setProperty (Ids::pluginAUIsSandboxSafe, "1", nullptr);
  291. setPropertyIfNotEmpty (Ids::pluginManufacturer, metadata[Ids::vendor]);
  292. StringArray pluginFormatsToBuild (Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildStandalone.toString());
  293. pluginFormatsToBuild.addArray (getExtraPluginFormatsToBuild());
  294. jucerTree.setProperty (Ids::pluginFormats, pluginFormatsToBuild.joinIntoString (","), nullptr);
  295. const auto characteristics = metadata[Ids::pluginCharacteristics].toString();
  296. if (characteristics.isNotEmpty())
  297. jucerTree.setProperty (Ids::pluginCharacteristicsValue,
  298. characteristics.removeCharacters (" \t\n\r"),
  299. nullptr);
  300. }
  301. jucerTree.setProperty (Ids::useAppConfig, false, nullptr);
  302. jucerTree.setProperty (Ids::addUsingNamespaceToJuceHeader, true, nullptr);
  303. return Result::ok();
  304. }
  305. void PIPGenerator::setModuleFlags (ValueTree& jucerTree)
  306. {
  307. ValueTree options ("JUCEOPTIONS");
  308. for (auto& option : StringArray::fromTokens (metadata[Ids::moduleFlags].toString(), ",", {}))
  309. {
  310. auto name = option.upToFirstOccurrenceOf ("=", false, true).trim();
  311. auto value = option.fromFirstOccurrenceOf ("=", false, true).trim();
  312. options.setProperty (name, (value == "1" ? 1 : 0), nullptr);
  313. }
  314. if (metadata[Ids::type].toString() == "AudioProcessor"
  315. && options.getPropertyPointer ("JUCE_VST3_CAN_REPLACE_VST2") == nullptr)
  316. options.setProperty ("JUCE_VST3_CAN_REPLACE_VST2", 0, nullptr);
  317. jucerTree.addChild (options, -1, nullptr);
  318. }
  319. String PIPGenerator::getMainFileTextForType()
  320. {
  321. const auto type = metadata[Ids::type].toString();
  322. const auto mainTemplate = [&]
  323. {
  324. if (type == "Console")
  325. return String (BinaryData::PIPConsole_cpp_in);
  326. if (type == "Component")
  327. return String (BinaryData::PIPComponent_cpp_in)
  328. .replace ("${JUCE_PIP_NAME}", metadata[Ids::name].toString())
  329. .replace ("${PROJECT_VERSION}", metadata[Ids::version].toString())
  330. .replace ("${JUCE_PIP_MAIN_CLASS}", metadata[Ids::mainClass].toString());
  331. if (type == "AudioProcessor")
  332. return String (BinaryData::PIPAudioProcessor_cpp_in)
  333. .replace ("${JUCE_PIP_MAIN_CLASS}", metadata[Ids::mainClass].toString());
  334. return String{};
  335. }();
  336. if (mainTemplate.isEmpty())
  337. return {};
  338. const auto includeFilename = [&]
  339. {
  340. if (useLocalCopy) return pipFile.getFileName();
  341. if (isTemp) return pipFile.getFullPathName();
  342. return build_tools::RelativePath (pipFile,
  343. outputDirectory.getChildFile ("Source"),
  344. build_tools::RelativePath::unknown).toUnixStyle();
  345. }();
  346. return ensureCorrectWhitespace (mainTemplate.replace ("${JUCE_PIP_HEADER}", includeFilename));
  347. }
  348. //==============================================================================
  349. Array<File> PIPGenerator::replaceRelativeIncludesAndGetFilesToMove()
  350. {
  351. StringArray lines;
  352. pipFile.readLines (lines);
  353. Array<File> files;
  354. for (auto& line : lines)
  355. {
  356. if (line.contains ("#include") && ! line.contains ("JuceLibraryCode"))
  357. {
  358. auto path = line.fromFirstOccurrenceOf ("#include", false, false);
  359. path = path.removeCharacters ("\"").trim();
  360. if (path.startsWith ("<") && path.endsWith (">"))
  361. continue;
  362. auto file = pipFile.getParentDirectory().getChildFile (path);
  363. files.add (file);
  364. line = line.replace (path, file.getFileName());
  365. }
  366. }
  367. outputDirectory.getChildFile ("Source")
  368. .getChildFile (pipFile.getFileName())
  369. .replaceWithText (joinLinesIntoSourceFile (lines));
  370. return files;
  371. }
  372. bool PIPGenerator::copyRelativeFileToLocalSourceDirectory (const File& fileToCopy) const noexcept
  373. {
  374. return fileToCopy.copyFileTo (outputDirectory.getChildFile ("Source")
  375. .getChildFile (fileToCopy.getFileName()));
  376. }
  377. StringArray PIPGenerator::getExtraPluginFormatsToBuild() const
  378. {
  379. auto tokens = StringArray::fromTokens (metadata[Ids::extraPluginFormats].toString(), ",", {});
  380. for (auto& token : tokens)
  381. {
  382. token = [&]
  383. {
  384. if (token == "IAA")
  385. return Ids::enableIAA.toString();
  386. return "build" + token;
  387. }();
  388. }
  389. return tokens;
  390. }
  391. String PIPGenerator::getPathForModule (const String& moduleID) const
  392. {
  393. if (isJUCEModule (moduleID))
  394. {
  395. if (juceModulesPath != File())
  396. {
  397. if (isTemp)
  398. return juceModulesPath.getFullPathName();
  399. return build_tools::RelativePath (juceModulesPath,
  400. outputDirectory,
  401. build_tools::RelativePath::projectFolder).toUnixStyle();
  402. }
  403. }
  404. else if (availableUserModules != nullptr)
  405. {
  406. auto moduleRoot = availableUserModules->getModuleWithID (moduleID).second.getParentDirectory();
  407. if (isTemp)
  408. return moduleRoot.getFullPathName();
  409. return build_tools::RelativePath (moduleRoot,
  410. outputDirectory,
  411. build_tools::RelativePath::projectFolder).toUnixStyle();
  412. }
  413. return {};
  414. }
  415. File PIPGenerator::getExamplesDirectory() const
  416. {
  417. if (juceModulesPath != File())
  418. {
  419. auto examples = juceModulesPath.getSiblingFile ("examples");
  420. if (isValidJUCEExamplesDirectory (examples))
  421. return examples;
  422. }
  423. auto examples = File (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString()).getChildFile ("examples");
  424. if (isValidJUCEExamplesDirectory (examples))
  425. return examples;
  426. return {};
  427. }