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.

588 lines
21KB

  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 "../../ProjectSaving/jucer_ProjectExport_MSVC.h"
  23. #include "jucer_PIPGenerator.h"
  24. //==============================================================================
  25. static void ensureSingleNewLineAfterIncludes (StringArray& lines)
  26. {
  27. int lastIncludeIndex = -1;
  28. for (int i = 0; i < lines.size(); ++i)
  29. {
  30. if (lines[i].contains ("#include"))
  31. lastIncludeIndex = i;
  32. }
  33. if (lastIncludeIndex != -1)
  34. {
  35. auto index = lastIncludeIndex;
  36. int numNewLines = 0;
  37. while (++index < lines.size() && lines[index].isEmpty())
  38. ++numNewLines;
  39. if (numNewLines > 1)
  40. lines.removeRange (lastIncludeIndex + 1, numNewLines - 1);
  41. }
  42. }
  43. static String ensureCorrectWhitespace (StringRef input)
  44. {
  45. auto lines = StringArray::fromLines (input);
  46. ensureSingleNewLineAfterIncludes (lines);
  47. return joinLinesIntoSourceFile (lines);
  48. }
  49. static bool isJUCEExample (const File& pipFile)
  50. {
  51. const auto numLinesToTest = 10; // license should be at the top of the file so no need to check all lines
  52. const auto lines = StringArray::fromLines (pipFile.loadFileAsString());
  53. return std::any_of (lines.begin(),
  54. lines.begin() + (std::min (lines.size(), numLinesToTest)),
  55. [] (const auto& line)
  56. {
  57. return line.contains ("This file is part of the JUCE examples.");
  58. });
  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. const Identifier vsExporters[] { MSVCProjectExporterVC2017::getValueTreeTypeName(),
  191. MSVCProjectExporterVC2019::getValueTreeTypeName(),
  192. MSVCProjectExporterVC2022::getValueTreeTypeName() };
  193. if (isJUCEExample (pipFile) && std::find (std::begin (vsExporters), std::end (vsExporters), exporterIdentifier) != std::end (vsExporters))
  194. {
  195. exporter.setProperty (Ids::extraCompilerFlags, "/bigobj", nullptr);
  196. }
  197. if (isJUCEExample (pipFile) && exporterRequiresExampleAssets (exporterIdentifier, metadata[Ids::name]))
  198. {
  199. auto examplesDir = getExamplesDirectory();
  200. if (examplesDir != File())
  201. {
  202. auto assetsDirectoryPath = examplesDir.getChildFile ("Assets").getFullPathName();
  203. exporter.setProperty (exporterIdentifier.toString() == AndroidProjectExporter::getValueTreeTypeName() ? Ids::androidExtraAssetsFolder
  204. : Ids::customXcodeResourceFolders,
  205. assetsDirectoryPath, nullptr);
  206. }
  207. else
  208. {
  209. // invalid JUCE path
  210. jassertfalse;
  211. }
  212. }
  213. if (exporterIdentifier.toString() == AndroidProjectExporter::getValueTreeTypeName())
  214. exporter.setProperty (Ids::androidBluetoothNeeded, true, nullptr);
  215. {
  216. ValueTree configs (Ids::CONFIGURATIONS);
  217. configs.addChild (createBuildConfigChild (true), -1, nullptr);
  218. configs.addChild (createBuildConfigChild (false), -1, nullptr);
  219. exporter.addChild (configs, -1, nullptr);
  220. }
  221. {
  222. ValueTree modulePaths (Ids::MODULEPATHS);
  223. auto modules = StringArray::fromTokens (metadata[Ids::dependencies_].toString(), ",", {});
  224. for (auto m : modules)
  225. modulePaths.addChild (createModulePathChild (m.trim()), -1, nullptr);
  226. exporter.addChild (modulePaths, -1, nullptr);
  227. }
  228. return exporter;
  229. }
  230. ValueTree PIPGenerator::createModuleChild (const String& moduleID)
  231. {
  232. ValueTree module (Ids::MODULE);
  233. module.setProperty (Ids::ID, moduleID, nullptr);
  234. module.setProperty (Ids::showAllCode, 1, nullptr);
  235. module.setProperty (Ids::useLocalCopy, 0, nullptr);
  236. module.setProperty (Ids::useGlobalPath, (getPathForModule (moduleID).isEmpty() ? 1 : 0), nullptr);
  237. return module;
  238. }
  239. void PIPGenerator::addExporters (ValueTree& jucerTree)
  240. {
  241. ValueTree exportersTree (Ids::EXPORTFORMATS);
  242. auto exporters = StringArray::fromTokens (metadata[Ids::exporters].toString(), ",", {});
  243. for (auto& e : exporters)
  244. {
  245. e = e.trim().toUpperCase();
  246. if (isValidExporterIdentifier (e))
  247. exportersTree.addChild (createExporterChild (e), -1, nullptr);
  248. }
  249. jucerTree.addChild (exportersTree, -1, nullptr);
  250. }
  251. void PIPGenerator::addModules (ValueTree& jucerTree)
  252. {
  253. ValueTree modulesTree (Ids::MODULES);
  254. auto modules = StringArray::fromTokens (metadata[Ids::dependencies_].toString(), ",", {});
  255. modules.trim();
  256. for (auto& m : modules)
  257. modulesTree.addChild (createModuleChild (m.trim()), -1, nullptr);
  258. jucerTree.addChild (modulesTree, -1, nullptr);
  259. }
  260. Result PIPGenerator::setProjectSettings (ValueTree& jucerTree)
  261. {
  262. auto setPropertyIfNotEmpty = [&jucerTree] (const Identifier& name, const var& value)
  263. {
  264. if (value != var())
  265. jucerTree.setProperty (name, value, nullptr);
  266. };
  267. setPropertyIfNotEmpty (Ids::name, metadata[Ids::name]);
  268. setPropertyIfNotEmpty (Ids::companyName, metadata[Ids::vendor]);
  269. setPropertyIfNotEmpty (Ids::version, metadata[Ids::version]);
  270. setPropertyIfNotEmpty (Ids::userNotes, metadata[Ids::description]);
  271. setPropertyIfNotEmpty (Ids::companyWebsite, metadata[Ids::website]);
  272. auto defines = metadata[Ids::defines].toString();
  273. if (isJUCEExample (pipFile))
  274. {
  275. auto examplesDir = getExamplesDirectory();
  276. if (examplesDir != File())
  277. {
  278. defines += ((defines.isEmpty() ? "" : " ") + String ("PIP_JUCE_EXAMPLES_DIRECTORY=")
  279. + Base64::toBase64 (examplesDir.getFullPathName()));
  280. }
  281. else
  282. {
  283. return Result::fail (String ("Invalid JUCE path. Set path to JUCE via ") +
  284. (TargetOS::getThisOS() == TargetOS::osx ? "\"Projucer->Global Paths...\""
  285. : "\"File->Global Paths...\"")
  286. + " menu item.");
  287. }
  288. jucerTree.setProperty (Ids::displaySplashScreen, true, nullptr);
  289. }
  290. setPropertyIfNotEmpty (Ids::defines, defines);
  291. auto type = metadata[Ids::type].toString();
  292. if (type == "Console")
  293. {
  294. jucerTree.setProperty (Ids::projectType, build_tools::ProjectType_ConsoleApp::getTypeName(), nullptr);
  295. }
  296. else if (type == "Component")
  297. {
  298. jucerTree.setProperty (Ids::projectType, build_tools::ProjectType_GUIApp::getTypeName(), nullptr);
  299. }
  300. else if (type == "AudioProcessor")
  301. {
  302. jucerTree.setProperty (Ids::projectType, build_tools::ProjectType_AudioPlugin::getTypeName(), nullptr);
  303. jucerTree.setProperty (Ids::pluginAUIsSandboxSafe, "1", nullptr);
  304. setPropertyIfNotEmpty (Ids::pluginManufacturer, metadata[Ids::vendor]);
  305. StringArray pluginFormatsToBuild (Ids::buildVST3.toString(), Ids::buildAU.toString(), Ids::buildStandalone.toString());
  306. pluginFormatsToBuild.addArray (getExtraPluginFormatsToBuild());
  307. if (getDocumentControllerClass().isNotEmpty())
  308. pluginFormatsToBuild.add (Ids::enableARA.toString());
  309. jucerTree.setProperty (Ids::pluginFormats, pluginFormatsToBuild.joinIntoString (","), nullptr);
  310. const auto characteristics = metadata[Ids::pluginCharacteristics].toString();
  311. if (characteristics.isNotEmpty())
  312. jucerTree.setProperty (Ids::pluginCharacteristicsValue,
  313. characteristics.removeCharacters (" \t\n\r"),
  314. nullptr);
  315. }
  316. jucerTree.setProperty (Ids::useAppConfig, false, nullptr);
  317. jucerTree.setProperty (Ids::addUsingNamespaceToJuceHeader, true, nullptr);
  318. return Result::ok();
  319. }
  320. void PIPGenerator::setModuleFlags (ValueTree& jucerTree)
  321. {
  322. ValueTree options ("JUCEOPTIONS");
  323. for (auto& option : StringArray::fromTokens (metadata[Ids::moduleFlags].toString(), ",", {}))
  324. {
  325. auto name = option.upToFirstOccurrenceOf ("=", false, true).trim();
  326. auto value = option.fromFirstOccurrenceOf ("=", false, true).trim();
  327. options.setProperty (name, (value == "1" ? 1 : 0), nullptr);
  328. }
  329. if (metadata[Ids::type].toString() == "AudioProcessor"
  330. && options.getPropertyPointer ("JUCE_VST3_CAN_REPLACE_VST2") == nullptr)
  331. options.setProperty ("JUCE_VST3_CAN_REPLACE_VST2", 0, nullptr);
  332. jucerTree.addChild (options, -1, nullptr);
  333. }
  334. String PIPGenerator::getMainFileTextForType()
  335. {
  336. const auto type = metadata[Ids::type].toString();
  337. const auto documentControllerClass = getDocumentControllerClass();
  338. const auto mainTemplate = [&]
  339. {
  340. if (type == "Console")
  341. return String (BinaryData::PIPConsole_cpp_in);
  342. if (type == "Component")
  343. return String (BinaryData::PIPComponent_cpp_in)
  344. .replace ("${JUCE_PIP_NAME}", metadata[Ids::name].toString())
  345. .replace ("${PROJECT_VERSION}", metadata[Ids::version].toString())
  346. .replace ("${JUCE_PIP_MAIN_CLASS}", metadata[Ids::mainClass].toString());
  347. if (type == "AudioProcessor")
  348. {
  349. if (documentControllerClass.isNotEmpty())
  350. {
  351. return String (BinaryData::PIPAudioProcessorWithARA_cpp_in)
  352. .replace ("${JUCE_PIP_MAIN_CLASS}", metadata[Ids::mainClass].toString())
  353. .replace ("${JUCE_PIP_DOCUMENTCONTROLLER_CLASS}", documentControllerClass);
  354. }
  355. return String (BinaryData::PIPAudioProcessor_cpp_in)
  356. .replace ("${JUCE_PIP_MAIN_CLASS}", metadata[Ids::mainClass].toString());
  357. }
  358. return String{};
  359. }();
  360. if (mainTemplate.isEmpty())
  361. return {};
  362. const auto includeFilename = [&]
  363. {
  364. if (useLocalCopy) return pipFile.getFileName();
  365. if (isTemp) return pipFile.getFullPathName();
  366. return build_tools::RelativePath (pipFile,
  367. outputDirectory.getChildFile ("Source"),
  368. build_tools::RelativePath::unknown).toUnixStyle();
  369. }();
  370. return ensureCorrectWhitespace (mainTemplate.replace ("${JUCE_PIP_HEADER}", includeFilename));
  371. }
  372. //==============================================================================
  373. Array<File> PIPGenerator::replaceRelativeIncludesAndGetFilesToMove()
  374. {
  375. StringArray lines;
  376. pipFile.readLines (lines);
  377. Array<File> files;
  378. for (auto& line : lines)
  379. {
  380. if (line.contains ("#include") && ! line.contains ("JuceLibraryCode"))
  381. {
  382. auto path = line.fromFirstOccurrenceOf ("#include", false, false);
  383. path = path.removeCharacters ("\"").trim();
  384. if (path.startsWith ("<") && path.endsWith (">"))
  385. continue;
  386. auto file = pipFile.getParentDirectory().getChildFile (path);
  387. files.add (file);
  388. line = line.replace (path, file.getFileName());
  389. }
  390. }
  391. outputDirectory.getChildFile ("Source")
  392. .getChildFile (pipFile.getFileName())
  393. .replaceWithText (joinLinesIntoSourceFile (lines));
  394. return files;
  395. }
  396. bool PIPGenerator::copyRelativeFileToLocalSourceDirectory (const File& fileToCopy) const noexcept
  397. {
  398. return fileToCopy.copyFileTo (outputDirectory.getChildFile ("Source")
  399. .getChildFile (fileToCopy.getFileName()));
  400. }
  401. StringArray PIPGenerator::getExtraPluginFormatsToBuild() const
  402. {
  403. auto tokens = StringArray::fromTokens (metadata[Ids::extraPluginFormats].toString(), ",", {});
  404. for (auto& token : tokens)
  405. {
  406. token = [&]
  407. {
  408. if (token == "IAA")
  409. return Ids::enableIAA.toString();
  410. return "build" + token;
  411. }();
  412. }
  413. return tokens;
  414. }
  415. String PIPGenerator::getPathForModule (const String& moduleID) const
  416. {
  417. if (isJUCEModule (moduleID))
  418. {
  419. if (juceModulesPath != File())
  420. {
  421. if (isTemp)
  422. return juceModulesPath.getFullPathName();
  423. return build_tools::RelativePath (juceModulesPath,
  424. outputDirectory,
  425. build_tools::RelativePath::projectFolder).toUnixStyle();
  426. }
  427. }
  428. else if (availableUserModules != nullptr)
  429. {
  430. auto moduleRoot = availableUserModules->getModuleWithID (moduleID).second.getParentDirectory();
  431. if (isTemp)
  432. return moduleRoot.getFullPathName();
  433. return build_tools::RelativePath (moduleRoot,
  434. outputDirectory,
  435. build_tools::RelativePath::projectFolder).toUnixStyle();
  436. }
  437. return {};
  438. }
  439. File PIPGenerator::getExamplesDirectory() const
  440. {
  441. if (juceModulesPath != File())
  442. {
  443. auto examples = juceModulesPath.getSiblingFile ("examples");
  444. if (isValidJUCEExamplesDirectory (examples))
  445. return examples;
  446. }
  447. auto examples = File (getAppSettings().getStoredPath (Ids::jucePath, TargetOS::getThisOS()).get().toString()).getChildFile ("examples");
  448. if (isValidJUCEExamplesDirectory (examples))
  449. return examples;
  450. return {};
  451. }