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.

511 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - ROLI Ltd.
  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 <juce_build_tools/juce_build_tools.h>
  14. #include <fstream>
  15. #include <unordered_map>
  16. namespace
  17. {
  18. constexpr auto headerTemplate = R"(/*
  19. IMPORTANT! This file is auto-generated.
  20. If you alter its contents, your changes may be overwritten!
  21. This is the header file that your files should include in order to get all the
  22. JUCE library headers. You should avoid including the JUCE headers directly in
  23. your own source files, because that wouldn't pick up the correct configuration
  24. options for your app.
  25. */
  26. #pragma once
  27. ${JUCE_INCLUDES}
  28. #if JUCE_TARGET_HAS_BINARY_DATA
  29. #include "BinaryData.h"
  30. #endif
  31. #if ! DONT_SET_USING_JUCE_NAMESPACE
  32. // If your code uses a lot of JUCE classes, then this will obviously save you
  33. // a lot of typing, but can be disabled by setting DONT_SET_USING_JUCE_NAMESPACE.
  34. using namespace juce;
  35. #endif
  36. #if ! JUCE_DONT_DECLARE_PROJECTINFO
  37. namespace ProjectInfo
  38. {
  39. const char* const projectName = "${JUCE_EXECUTABLE_NAME}";
  40. const char* const companyName = "${JUCE_COMPANY_NAME}";
  41. const char* const versionString = "${JUCE_PROJECT_VERSION}";
  42. const int versionNumber = ${JUCE_PROJECT_VERSION_HEX};
  43. }
  44. #endif
  45. )";
  46. juce::String getValueOr (juce::ArgumentList& args, juce::String option, juce::String fallback)
  47. {
  48. const auto opt = args.removeValueForOption (option);
  49. return opt.isNotEmpty() ? opt : fallback;
  50. }
  51. int writeBinaryData (juce::ArgumentList&& argumentList)
  52. {
  53. juce::build_tools::ResourceFile resourceFile;
  54. resourceFile.setClassName (getValueOr (argumentList, "--namespace", "BinaryData"));
  55. const auto lineEndings = argumentList.removeOptionIfFound ("--windows") ? "\r\n" : "\n";
  56. if (argumentList.arguments.isEmpty())
  57. juce::ConsoleApplication::fail ("No destination folder specified for binary data files", 1);
  58. const auto outFolder = argumentList.arguments.removeAndReturn (0).resolveAsExistingFolder();
  59. for (const auto& arg : argumentList.arguments)
  60. resourceFile.addFile (arg.resolveAsExistingFile());
  61. const auto result = resourceFile.write (0,
  62. lineEndings,
  63. outFolder.getChildFile ("./BinaryData.h"),
  64. [&outFolder] (int index)
  65. {
  66. return outFolder.getChildFile ("./BinaryData" + juce::String { index + 1 } + ".cpp");
  67. });
  68. if (result.result.failed())
  69. juce::ConsoleApplication::fail (result.result.getErrorMessage(), 1);
  70. return 0;
  71. }
  72. struct IconParseResults final
  73. {
  74. juce::build_tools::Icons icons;
  75. juce::File output;
  76. };
  77. IconParseResults parseIconArguments (juce::ArgumentList&& args)
  78. {
  79. args.checkMinNumArguments (2);
  80. const auto output = args.arguments.removeAndReturn (0);
  81. const auto popDrawable = [&args]() -> std::unique_ptr<juce::Drawable>
  82. {
  83. if (args.size() == 0)
  84. return {};
  85. const auto firstArgText = args.arguments.removeAndReturn (0).text;
  86. return juce::Drawable::createFromImageFile (firstArgText);
  87. };
  88. auto smallIcon = popDrawable();
  89. auto bigIcon = popDrawable();
  90. return { { std::move (smallIcon), std::move (bigIcon) }, output.text };
  91. }
  92. int writeMacIcon (juce::ArgumentList&& argumentList)
  93. {
  94. const auto parsed = parseIconArguments (std::move (argumentList));
  95. juce::build_tools::writeMacIcon (parsed.icons, parsed.output);
  96. return 0;
  97. }
  98. int writeiOSAssets (juce::ArgumentList&& argumentList)
  99. {
  100. const auto parsed = parseIconArguments (std::move (argumentList));
  101. juce::build_tools::createXcassetsFolderFromIcons (parsed.icons,
  102. parsed.output.getParentDirectory(),
  103. parsed.output.getFileName());
  104. return 0;
  105. }
  106. int writeWinIcon (juce::ArgumentList&& argumentList)
  107. {
  108. const auto parsed = parseIconArguments (std::move (argumentList));
  109. juce::build_tools::writeWinIcon (parsed.icons, parsed.output);
  110. return 0;
  111. }
  112. std::unordered_map<juce::String, juce::String> parseProjectData (const juce::File& file)
  113. {
  114. constexpr auto recordSeparator = "\x1e";
  115. const auto contents = file.loadFileAsString();
  116. const auto lines = juce::StringArray::fromTokens (contents, recordSeparator, {});
  117. std::unordered_map<juce::String, juce::String> result;
  118. constexpr auto unitSeparator = "\x1f";
  119. for (const auto& line : lines)
  120. {
  121. if (line.isEmpty())
  122. continue;
  123. result.emplace (line.upToFirstOccurrenceOf (unitSeparator, false, false),
  124. line.fromFirstOccurrenceOf (unitSeparator, false, false));
  125. }
  126. return result;
  127. }
  128. juce::String getStringValue (const std::unordered_map<juce::String, juce::String>& dict,
  129. juce::StringRef key)
  130. {
  131. const auto it = dict.find (key);
  132. return it != dict.cend() ? it->second : juce::String{};
  133. }
  134. bool getBoolValue (const std::unordered_map<juce::String, juce::String>& dict, juce::StringRef key)
  135. {
  136. const auto str = getStringValue (dict, key);
  137. return str.equalsIgnoreCase ("yes")
  138. || str.equalsIgnoreCase ("true")
  139. || str.equalsIgnoreCase ("1")
  140. || str.equalsIgnoreCase ("on");
  141. }
  142. struct UpdateField final
  143. {
  144. const std::unordered_map<juce::String, juce::String>& dict;
  145. void operator() (juce::StringRef key, juce::String& value) const
  146. {
  147. value = getStringValue (dict, key);
  148. }
  149. void operator() (juce::StringRef key, juce::File& value) const
  150. {
  151. value = getStringValue (dict, key);
  152. }
  153. void operator() (juce::StringRef key, bool& value) const
  154. {
  155. value = getBoolValue (dict, key);
  156. }
  157. void operator() (juce::StringRef key, juce::StringArray& value) const
  158. {
  159. value = juce::StringArray::fromTokens (getStringValue (dict, key), ";", {});
  160. }
  161. };
  162. void setIfEmpty (juce::String& field, juce::StringRef fallback)
  163. {
  164. if (field.isEmpty())
  165. field = fallback;
  166. }
  167. juce::build_tools::PlistOptions parsePlistOptions (const juce::File& file,
  168. juce::build_tools::ProjectType::Target::Type type)
  169. {
  170. if (type == juce::build_tools::ProjectType::Target::ConsoleApp)
  171. juce::ConsoleApplication::fail ("Deduced project type does not require a plist", 1);
  172. const auto dict = parseProjectData (file);
  173. UpdateField updateField { dict };
  174. juce::build_tools::PlistOptions result;
  175. updateField ("EXECUTABLE_NAME", result.executableName);
  176. updateField ("PLIST_TO_MERGE", result.plistToMerge);
  177. updateField ("IS_IOS", result.iOS);
  178. updateField ("MICROPHONE_PERMISSION_ENABLED", result.microphonePermissionEnabled);
  179. updateField ("MICROPHONE_PERMISSION_TEXT", result.microphonePermissionText);
  180. updateField ("CAMERA_PERMISSION_ENABLED", result.cameraPermissionEnabled);
  181. updateField ("CAMERA_PERMISSION_TEXT", result.cameraPermissionText);
  182. updateField ("BLUETOOTH_PERMISSION_ENABLED", result.bluetoothPermissionEnabled);
  183. updateField ("BLUETOOTH_PERMISSION_TEXT", result.bluetoothPermissionText);
  184. updateField ("SEND_APPLE_EVENTS_PERMISSION_ENABLED", result.sendAppleEventsPermissionEnabled);
  185. updateField ("SEND_APPLE_EVENTS_PERMISSION_TEXT", result.sendAppleEventsPermissionText);
  186. updateField ("SHOULD_ADD_STORYBOARD", result.shouldAddStoryboardToProject);
  187. updateField ("LAUNCH_STORYBOARD_FILE", result.storyboardName);
  188. updateField ("PROJECT_NAME", result.projectName);
  189. updateField ("VERSION", result.version);
  190. updateField ("COMPANY_COPYRIGHT", result.companyCopyright);
  191. updateField ("DOCUMENT_EXTENSIONS", result.documentExtensions);
  192. updateField ("FILE_SHARING_ENABLED", result.fileSharingEnabled);
  193. updateField ("DOCUMENT_BROWSER_ENABLED", result.documentBrowserEnabled);
  194. updateField ("STATUS_BAR_HIDDEN", result.statusBarHidden);
  195. updateField ("BACKGROUND_AUDIO_ENABLED", result.backgroundAudioEnabled);
  196. updateField ("BACKGROUND_BLE_ENABLED", result.backgroundBleEnabled);
  197. updateField ("PUSH_NOTIFICATIONS_ENABLED", result.pushNotificationsEnabled);
  198. updateField ("PLUGIN_MANUFACTURER_CODE", result.pluginManufacturerCode);
  199. updateField ("PLUGIN_CODE", result.pluginCode);
  200. updateField ("IPHONE_SCREEN_ORIENTATIONS", result.iPhoneScreenOrientations);
  201. updateField ("IPAD_SCREEN_ORIENTATIONS", result.iPadScreenOrientations);
  202. updateField ("PLUGIN_NAME", result.pluginName);
  203. updateField ("PLUGIN_MANUFACTURER", result.pluginManufacturer);
  204. updateField ("PLUGIN_DESCRIPTION", result.pluginDescription);
  205. updateField ("PLUGIN_AU_EXPORT_PREFIX", result.pluginAUExportPrefix);
  206. updateField ("PLUGIN_AU_MAIN_TYPE", result.auMainType);
  207. updateField ("IS_AU_SANDBOX_SAFE", result.isAuSandboxSafe);
  208. updateField ("IS_PLUGIN_SYNTH", result.isPluginSynth);
  209. updateField ("BUNDLE_ID", result.bundleIdentifier);
  210. updateField ("ICON_FILE", result.iconFile);
  211. result.type = type;
  212. result.versionAsHex = juce::build_tools::getVersionAsHexInteger (result.version);
  213. if (result.storyboardName.isNotEmpty())
  214. result.storyboardName = result.storyboardName.fromLastOccurrenceOf ("/", false, false)
  215. .upToLastOccurrenceOf (".storyboard", false, false);
  216. setIfEmpty (result.microphonePermissionText,
  217. "This app requires audio input. If you do not have an audio interface connected it will use the built-in microphone.");
  218. setIfEmpty (result.cameraPermissionText,
  219. "This app requires access to the camera to function correctly.");
  220. setIfEmpty (result.bluetoothPermissionText,
  221. "This app requires access to Bluetooth to function correctly.");
  222. setIfEmpty (result.sendAppleEventsPermissionText,
  223. "This app requires the ability to send Apple events to function correctly.");
  224. result.documentExtensions = result.documentExtensions.replace (";", ",");
  225. // AUv3 needs a slightly different bundle ID
  226. if (type == juce::build_tools::ProjectType::Target::Type::AudioUnitv3PlugIn)
  227. {
  228. const auto bundleIdSegments = juce::StringArray::fromTokens (result.bundleIdentifier, ".", {});
  229. jassert (! bundleIdSegments.isEmpty());
  230. const auto last = bundleIdSegments.isEmpty() ? ""
  231. : bundleIdSegments[bundleIdSegments.size() - 1];
  232. result.bundleIdentifier += "." + last + "AUv3";
  233. }
  234. return result;
  235. }
  236. int writePlist (juce::ArgumentList&& args)
  237. {
  238. args.checkMinNumArguments (3);
  239. const auto kind = args.arguments.removeAndReturn (0);
  240. const auto input = args.arguments.removeAndReturn (0);
  241. const auto output = args.arguments.removeAndReturn (0);
  242. parsePlistOptions (input.resolveAsExistingFile(),
  243. juce::build_tools::ProjectType::Target::typeFromName (kind.text))
  244. .write (output.resolveAsFile());
  245. return 0;
  246. }
  247. juce::build_tools::EntitlementOptions parseEntitlementsOptions (const juce::File& file,
  248. juce::build_tools::ProjectType::Target::Type type)
  249. {
  250. if (type == juce::build_tools::ProjectType::Target::ConsoleApp)
  251. juce::ConsoleApplication::fail ("Deduced project type does not require entitlements", 1);
  252. const auto dict = parseProjectData (file);
  253. UpdateField updateField { dict };
  254. juce::build_tools::EntitlementOptions result;
  255. updateField ("IS_IOS", result.isiOS);
  256. updateField ("IS_PLUGIN", result.isAudioPluginProject);
  257. updateField ("ICLOUD_PERMISSIONS_ENABLED", result.isiCloudPermissionsEnabled);
  258. updateField ("PUSH_NOTIFICATIONS_ENABLED", result.isPushNotificationsEnabled);
  259. updateField ("APP_GROUPS_ENABLED", result.isAppGroupsEnabled);
  260. updateField ("APP_GROUP_IDS", result.appGroupIdString);
  261. updateField ("HARDENED_RUNTIME_ENABLED", result.isHardenedRuntimeEnabled);
  262. updateField ("HARDENED_RUNTIME_OPTIONS", result.hardenedRuntimeOptions);
  263. updateField ("APP_SANDBOX_ENABLED", result.isAppSandboxEnabled);
  264. updateField ("APP_SANDBOX_INHERIT", result.isAppSandboxInhertianceEnabled);
  265. updateField ("APP_SANDBOX_OPTIONS", result.appSandboxOptions);
  266. result.type = type;
  267. return result;
  268. }
  269. int writeEntitlements (juce::ArgumentList&& args)
  270. {
  271. args.checkMinNumArguments (3);
  272. const auto kind = args.arguments.removeAndReturn (0);
  273. const auto input = args.arguments.removeAndReturn (0);
  274. const auto output = args.arguments.removeAndReturn (0);
  275. const auto options = parseEntitlementsOptions (input.resolveAsExistingFile(),
  276. juce::build_tools::ProjectType::Target::typeFromName (kind.text));
  277. juce::build_tools::overwriteFileIfDifferentOrThrow (output.resolveAsFile(), options.getEntitlementsFileContent());
  278. return 0;
  279. }
  280. int createAndWrite (const juce::File& file, juce::StringRef text)
  281. {
  282. if (file.create())
  283. return file.replaceWithText (text) ? 0 : 1;
  284. return 1;
  285. }
  286. int writePkgInfo (juce::ArgumentList&& args)
  287. {
  288. args.checkMinNumArguments (2);
  289. const auto kind = args.arguments.removeAndReturn (0);
  290. const auto output = args.arguments.removeAndReturn (0);
  291. const auto projectType = juce::build_tools::ProjectType::Target::typeFromName (kind.text);
  292. return createAndWrite (output.resolveAsFile(),
  293. juce::build_tools::getXcodePackageType (projectType)
  294. + juce::build_tools::getXcodeBundleSignature (projectType));
  295. }
  296. juce::build_tools::ResourceRcOptions parseRcFileOptions (const juce::File& file)
  297. {
  298. const auto dict = parseProjectData (file);
  299. UpdateField updateField { dict };
  300. juce::build_tools::ResourceRcOptions result;
  301. updateField ("VERSION", result.version);
  302. updateField ("COMPANY_NAME", result.companyName);
  303. updateField ("COMPANY_COPYRIGHT", result.companyCopyright);
  304. updateField ("PROJECT_NAME", result.projectName);
  305. updateField ("ICON_FILE", result.icon);
  306. return result;
  307. }
  308. int writeRcFile (juce::ArgumentList&& args)
  309. {
  310. args.checkMinNumArguments (2);
  311. const auto input = args.arguments.removeAndReturn (0);
  312. const auto output = args.arguments.removeAndReturn (0);
  313. parseRcFileOptions (input.resolveAsExistingFile()).write (output.resolveAsFile());
  314. return 0;
  315. }
  316. juce::String createDefineStatements (juce::StringRef definitions)
  317. {
  318. const auto split = juce::StringArray::fromTokens (definitions, ";", "\"");
  319. juce::String defineStatements;
  320. for (const auto& def : split)
  321. {
  322. if (! def.startsWith ("JucePlugin_"))
  323. continue;
  324. const auto defineName = def.upToFirstOccurrenceOf ("=", false, false);
  325. const auto defineValue = def.fromFirstOccurrenceOf ("=", false, false);
  326. defineStatements += "#define " + defineName + " " + defineValue + '\n';
  327. }
  328. return defineStatements;
  329. }
  330. int writeAuPluginDefines (juce::ArgumentList&& args)
  331. {
  332. args.checkMinNumArguments (2);
  333. const auto input = args.arguments.removeAndReturn (0);
  334. const auto output = args.arguments.removeAndReturn (0);
  335. const auto dict = parseProjectData (input.resolveAsExistingFile());
  336. const auto getString = [&] (juce::StringRef key) { return getStringValue (dict, key); };
  337. const auto defines = "#pragma once\n" + createDefineStatements (getString ("MODULE_DEFINITIONS"));
  338. return createAndWrite (output.resolveAsFile(), defines);
  339. }
  340. juce::String createIncludeStatements (juce::StringRef definitions)
  341. {
  342. const auto split = juce::StringArray::fromTokens (definitions, ";", "\"");
  343. juce::String includeStatements;
  344. for (const auto& def : split)
  345. {
  346. constexpr auto moduleToken = "JUCE_MODULE_AVAILABLE_";
  347. if (def.startsWith (moduleToken))
  348. {
  349. const auto moduleName = def.fromFirstOccurrenceOf (moduleToken, false, false)
  350. .upToFirstOccurrenceOf ("=", false, false);
  351. includeStatements += "#include <" + moduleName + "/" + moduleName + ".h>\n";
  352. }
  353. }
  354. return includeStatements;
  355. }
  356. int writeHeader (juce::ArgumentList&& args)
  357. {
  358. args.checkMinNumArguments (2);
  359. const auto input = args.arguments.removeAndReturn (0);
  360. const auto output = args.arguments.removeAndReturn (0);
  361. const auto dict = parseProjectData (input.resolveAsExistingFile());
  362. const auto getString = [&] (juce::StringRef key) { return getStringValue (dict, key); };
  363. const auto includes = createIncludeStatements (getString ("MODULE_DEFINITIONS"));
  364. const auto projectName = getString ("PROJECT_NAME");
  365. const auto name = projectName.isEmpty() ? getString ("EXECUTABLE_NAME") : projectName;
  366. const auto versionString = getString ("VERSION");
  367. const auto headerText = juce::String (headerTemplate)
  368. .replace ("${JUCE_INCLUDES}", includes)
  369. .replace ("${JUCE_EXECUTABLE_NAME}", name)
  370. .replace ("${JUCE_COMPANY_NAME}", getString ("COMPANY_NAME"))
  371. .replace ("${JUCE_PROJECT_VERSION}", versionString)
  372. .replace ("${JUCE_PROJECT_VERSION_HEX}", juce::build_tools::getVersionAsHex (versionString));
  373. return createAndWrite (output.resolveAsFile(), headerText);
  374. }
  375. } // namespace
  376. int main (int argc, char** argv)
  377. {
  378. juce::ScopedJuceInitialiser_GUI libraryInitialiser;
  379. return juce::ConsoleApplication::invokeCatchingFailures ([argc, argv]
  380. {
  381. juce::ArgumentList argumentList { argc, argv };
  382. using Fn = typename std::add_lvalue_reference<decltype (writeBinaryData)>::type;
  383. const std::unordered_map<juce::String, Fn> commands
  384. {
  385. { "auplugindefines", writeAuPluginDefines },
  386. { "binarydata", writeBinaryData },
  387. { "entitlements", writeEntitlements },
  388. { "header", writeHeader },
  389. { "iosassets", writeiOSAssets },
  390. { "macicon", writeMacIcon },
  391. { "pkginfo", writePkgInfo },
  392. { "plist", writePlist },
  393. { "rcfile", writeRcFile },
  394. { "winicon", writeWinIcon }
  395. };
  396. argumentList.checkMinNumArguments (1);
  397. const auto mode = argumentList.arguments.removeAndReturn (0);
  398. const auto it = commands.find (mode.text);
  399. if (it == commands.cend())
  400. juce::ConsoleApplication::fail ("No matching mode", 1);
  401. return it->second (std::move (argumentList));
  402. });
  403. }