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.

578 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - 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 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-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. String PIPGenerator::getDocumentControllerClass() const
  167. {
  168. return metadata.getProperty (Ids::documentControllerClass, "").toString();
  169. }
  170. ValueTree PIPGenerator::createModulePathChild (const String& moduleID)
  171. {
  172. ValueTree modulePath (Ids::MODULEPATH);
  173. modulePath.setProperty (Ids::ID, moduleID, nullptr);
  174. modulePath.setProperty (Ids::path, getPathForModule (moduleID), nullptr);
  175. return modulePath;
  176. }
  177. ValueTree PIPGenerator::createBuildConfigChild (bool isDebug)
  178. {
  179. ValueTree child (Ids::CONFIGURATION);
  180. child.setProperty (Ids::name, isDebug ? "Debug" : "Release", nullptr);
  181. child.setProperty (Ids::isDebug, isDebug ? 1 : 0, nullptr);
  182. child.setProperty (Ids::optimisation, isDebug ? 1 : 3, nullptr);
  183. child.setProperty (Ids::targetName, metadata[Ids::name], nullptr);
  184. return child;
  185. }
  186. ValueTree PIPGenerator::createExporterChild (const Identifier& exporterIdentifier)
  187. {
  188. ValueTree exporter (exporterIdentifier);
  189. exporter.setProperty (Ids::targetFolder, "Builds/" + ProjectExporter::getTypeInfoForExporter (exporterIdentifier).targetFolder, nullptr);
  190. if (isJUCEExample (pipFile) && exporterRequiresExampleAssets (exporterIdentifier, metadata[Ids::name]))
  191. {
  192. auto examplesDir = getExamplesDirectory();
  193. if (examplesDir != File())
  194. {
  195. auto assetsDirectoryPath = examplesDir.getChildFile ("Assets").getFullPathName();
  196. exporter.setProperty (exporterIdentifier.toString() == AndroidProjectExporter::getValueTreeTypeName() ? Ids::androidExtraAssetsFolder
  197. : Ids::customXcodeResourceFolders,
  198. assetsDirectoryPath, nullptr);
  199. }
  200. else
  201. {
  202. // invalid JUCE path
  203. jassertfalse;
  204. }
  205. }
  206. {
  207. ValueTree configs (Ids::CONFIGURATIONS);
  208. configs.addChild (createBuildConfigChild (true), -1, nullptr);
  209. configs.addChild (createBuildConfigChild (false), -1, nullptr);
  210. exporter.addChild (configs, -1, nullptr);
  211. }
  212. {
  213. ValueTree modulePaths (Ids::MODULEPATHS);
  214. auto modules = StringArray::fromTokens (metadata[Ids::dependencies_].toString(), ",", {});
  215. for (auto m : modules)
  216. modulePaths.addChild (createModulePathChild (m.trim()), -1, nullptr);
  217. exporter.addChild (modulePaths, -1, nullptr);
  218. }
  219. return exporter;
  220. }
  221. ValueTree PIPGenerator::createModuleChild (const String& moduleID)
  222. {
  223. ValueTree module (Ids::MODULE);
  224. module.setProperty (Ids::ID, moduleID, nullptr);
  225. module.setProperty (Ids::showAllCode, 1, nullptr);
  226. module.setProperty (Ids::useLocalCopy, 0, nullptr);
  227. module.setProperty (Ids::useGlobalPath, (getPathForModule (moduleID).isEmpty() ? 1 : 0), nullptr);
  228. return module;
  229. }
  230. void PIPGenerator::addExporters (ValueTree& jucerTree)
  231. {
  232. ValueTree exportersTree (Ids::EXPORTFORMATS);
  233. auto exporters = StringArray::fromTokens (metadata[Ids::exporters].toString(), ",", {});
  234. for (auto& e : exporters)
  235. {
  236. e = e.trim().toUpperCase();
  237. if (isValidExporterIdentifier (e))
  238. exportersTree.addChild (createExporterChild (e), -1, nullptr);
  239. }
  240. jucerTree.addChild (exportersTree, -1, nullptr);
  241. }
  242. void PIPGenerator::addModules (ValueTree& jucerTree)
  243. {
  244. ValueTree modulesTree (Ids::MODULES);
  245. auto modules = StringArray::fromTokens (metadata[Ids::dependencies_].toString(), ",", {});
  246. modules.trim();
  247. for (auto& m : modules)
  248. modulesTree.addChild (createModuleChild (m.trim()), -1, nullptr);
  249. jucerTree.addChild (modulesTree, -1, nullptr);
  250. }
  251. Result PIPGenerator::setProjectSettings (ValueTree& jucerTree)
  252. {
  253. auto setPropertyIfNotEmpty = [&jucerTree] (const Identifier& name, const var& value)
  254. {
  255. if (value != var())
  256. jucerTree.setProperty (name, value, nullptr);
  257. };
  258. setPropertyIfNotEmpty (Ids::name, metadata[Ids::name]);
  259. setPropertyIfNotEmpty (Ids::companyName, metadata[Ids::vendor]);
  260. setPropertyIfNotEmpty (Ids::version, metadata[Ids::version]);
  261. setPropertyIfNotEmpty (Ids::userNotes, metadata[Ids::description]);
  262. setPropertyIfNotEmpty (Ids::companyWebsite, metadata[Ids::website]);
  263. auto defines = metadata[Ids::defines].toString();
  264. if (isJUCEExample (pipFile))
  265. {
  266. auto examplesDir = getExamplesDirectory();
  267. if (examplesDir != File())
  268. {
  269. defines += ((defines.isEmpty() ? "" : " ") + String ("PIP_JUCE_EXAMPLES_DIRECTORY=")
  270. + Base64::toBase64 (examplesDir.getFullPathName()));
  271. }
  272. else
  273. {
  274. return Result::fail (String ("Invalid JUCE path. Set path to JUCE via ") +
  275. (TargetOS::getThisOS() == TargetOS::osx ? "\"Projucer->Global Paths...\""
  276. : "\"File->Global Paths...\"")
  277. + " menu item.");
  278. }
  279. jucerTree.setProperty (Ids::displaySplashScreen, true, nullptr);
  280. }
  281. setPropertyIfNotEmpty (Ids::defines, defines);
  282. auto type = metadata[Ids::type].toString();
  283. if (type == "Console")
  284. {
  285. jucerTree.setProperty (Ids::projectType, build_tools::ProjectType_ConsoleApp::getTypeName(), nullptr);
  286. }
  287. else if (type == "Component")
  288. {
  289. jucerTree.setProperty (Ids::projectType, build_tools::ProjectType_GUIApp::getTypeName(), nullptr);
  290. }
  291. else if (type == "AudioProcessor")
  292. {
  293. jucerTree.setProperty (Ids::projectType, build_tools::ProjectType_AudioPlugin::getTypeName(), nullptr);
  294. jucerTree.setProperty (Ids::pluginAUIsSandboxSafe, "1", nullptr);
  295. setPropertyIfNotEmpty (Ids::pluginManufacturer, metadata[Ids::vendor]);
  296. StringArray pluginFormatsToBuild (Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildStandalone.toString());
  297. pluginFormatsToBuild.addArray (getExtraPluginFormatsToBuild());
  298. if (getDocumentControllerClass().isNotEmpty())
  299. pluginFormatsToBuild.add (Ids::enableARA.toString());
  300. jucerTree.setProperty (Ids::pluginFormats, pluginFormatsToBuild.joinIntoString (","), nullptr);
  301. const auto characteristics = metadata[Ids::pluginCharacteristics].toString();
  302. if (characteristics.isNotEmpty())
  303. jucerTree.setProperty (Ids::pluginCharacteristicsValue,
  304. characteristics.removeCharacters (" \t\n\r"),
  305. nullptr);
  306. }
  307. jucerTree.setProperty (Ids::useAppConfig, false, nullptr);
  308. jucerTree.setProperty (Ids::addUsingNamespaceToJuceHeader, true, nullptr);
  309. return Result::ok();
  310. }
  311. void PIPGenerator::setModuleFlags (ValueTree& jucerTree)
  312. {
  313. ValueTree options ("JUCEOPTIONS");
  314. for (auto& option : StringArray::fromTokens (metadata[Ids::moduleFlags].toString(), ",", {}))
  315. {
  316. auto name = option.upToFirstOccurrenceOf ("=", false, true).trim();
  317. auto value = option.fromFirstOccurrenceOf ("=", false, true).trim();
  318. options.setProperty (name, (value == "1" ? 1 : 0), nullptr);
  319. }
  320. if (metadata[Ids::type].toString() == "AudioProcessor"
  321. && options.getPropertyPointer ("JUCE_VST3_CAN_REPLACE_VST2") == nullptr)
  322. options.setProperty ("JUCE_VST3_CAN_REPLACE_VST2", 0, nullptr);
  323. jucerTree.addChild (options, -1, nullptr);
  324. }
  325. String PIPGenerator::getMainFileTextForType()
  326. {
  327. const auto type = metadata[Ids::type].toString();
  328. const auto documentControllerClass = getDocumentControllerClass();
  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. {
  340. if (documentControllerClass.isNotEmpty())
  341. {
  342. return String (BinaryData::PIPAudioProcessorWithARA_cpp_in)
  343. .replace ("${JUCE_PIP_MAIN_CLASS}", metadata[Ids::mainClass].toString())
  344. .replace ("${JUCE_PIP_DOCUMENTCONTROLLER_CLASS}", documentControllerClass);
  345. }
  346. return String (BinaryData::PIPAudioProcessor_cpp_in)
  347. .replace ("${JUCE_PIP_MAIN_CLASS}", metadata[Ids::mainClass].toString());
  348. }
  349. return String{};
  350. }();
  351. if (mainTemplate.isEmpty())
  352. return {};
  353. const auto includeFilename = [&]
  354. {
  355. if (useLocalCopy) return pipFile.getFileName();
  356. if (isTemp) return pipFile.getFullPathName();
  357. return build_tools::RelativePath (pipFile,
  358. outputDirectory.getChildFile ("Source"),
  359. build_tools::RelativePath::unknown).toUnixStyle();
  360. }();
  361. return ensureCorrectWhitespace (mainTemplate.replace ("${JUCE_PIP_HEADER}", includeFilename));
  362. }
  363. //==============================================================================
  364. Array<File> PIPGenerator::replaceRelativeIncludesAndGetFilesToMove()
  365. {
  366. StringArray lines;
  367. pipFile.readLines (lines);
  368. Array<File> files;
  369. for (auto& line : lines)
  370. {
  371. if (line.contains ("#include") && ! line.contains ("JuceLibraryCode"))
  372. {
  373. auto path = line.fromFirstOccurrenceOf ("#include", false, false);
  374. path = path.removeCharacters ("\"").trim();
  375. if (path.startsWith ("<") && path.endsWith (">"))
  376. continue;
  377. auto file = pipFile.getParentDirectory().getChildFile (path);
  378. files.add (file);
  379. line = line.replace (path, file.getFileName());
  380. }
  381. }
  382. outputDirectory.getChildFile ("Source")
  383. .getChildFile (pipFile.getFileName())
  384. .replaceWithText (joinLinesIntoSourceFile (lines));
  385. return files;
  386. }
  387. bool PIPGenerator::copyRelativeFileToLocalSourceDirectory (const File& fileToCopy) const noexcept
  388. {
  389. return fileToCopy.copyFileTo (outputDirectory.getChildFile ("Source")
  390. .getChildFile (fileToCopy.getFileName()));
  391. }
  392. StringArray PIPGenerator::getExtraPluginFormatsToBuild() const
  393. {
  394. auto tokens = StringArray::fromTokens (metadata[Ids::extraPluginFormats].toString(), ",", {});
  395. for (auto& token : tokens)
  396. {
  397. token = [&]
  398. {
  399. if (token == "IAA")
  400. return Ids::enableIAA.toString();
  401. return "build" + token;
  402. }();
  403. }
  404. return tokens;
  405. }
  406. String PIPGenerator::getPathForModule (const String& moduleID) const
  407. {
  408. if (isJUCEModule (moduleID))
  409. {
  410. if (juceModulesPath != File())
  411. {
  412. if (isTemp)
  413. return juceModulesPath.getFullPathName();
  414. return build_tools::RelativePath (juceModulesPath,
  415. outputDirectory,
  416. build_tools::RelativePath::projectFolder).toUnixStyle();
  417. }
  418. }
  419. else if (availableUserModules != nullptr)
  420. {
  421. auto moduleRoot = availableUserModules->getModuleWithID (moduleID).second.getParentDirectory();
  422. if (isTemp)
  423. return moduleRoot.getFullPathName();
  424. return build_tools::RelativePath (moduleRoot,
  425. outputDirectory,
  426. build_tools::RelativePath::projectFolder).toUnixStyle();
  427. }
  428. return {};
  429. }
  430. File PIPGenerator::getExamplesDirectory() const
  431. {
  432. if (juceModulesPath != File())
  433. {
  434. auto examples = juceModulesPath.getSiblingFile ("examples");
  435. if (isValidJUCEExamplesDirectory (examples))
  436. return examples;
  437. }
  438. auto examples = File (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString()).getChildFile ("examples");
  439. if (isValidJUCEExamplesDirectory (examples))
  440. return examples;
  441. return {};
  442. }