1 /* 2 ============================================================================== 3 4 This file is part of the JUCE library. 5 Copyright (c) 2020 - Raw Material Software Limited 6 7 JUCE is an open source library subject to commercial or open-source 8 licensing. 9 10 By using JUCE, you agree to the terms of both the JUCE 6 End-User License 11 Agreement and JUCE Privacy Policy (both effective as of the 16th June 2020). 12 13 End User License Agreement: www.juce.com/juce-6-licence 14 Privacy Policy: www.juce.com/juce-privacy-policy 15 16 Or: You may also use this code under the terms of the GPL v3 (see 17 www.gnu.org/licenses). 18 19 JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER 20 EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE 21 DISCLAIMED. 22 23 ============================================================================== 24 */ 25 26 namespace juce 27 { 28 namespace build_tools 29 { 30 //============================================================================== keyFoundAndNotSequentialDuplicate(XmlElement & xml,const String & key)31 static bool keyFoundAndNotSequentialDuplicate (XmlElement& xml, const String& key) 32 { 33 for (auto* element : xml.getChildWithTagNameIterator ("key")) 34 { 35 if (element->getAllSubText().trim().equalsIgnoreCase (key)) 36 { 37 if (element->getNextElement() != nullptr && element->getNextElement()->hasTagName ("key")) 38 { 39 // found broken plist format (sequential duplicate), fix by removing 40 xml.removeChildElement (element, true); 41 return false; 42 } 43 44 // key found (not sequential duplicate) 45 return true; 46 } 47 } 48 49 // key not found 50 return false; 51 } 52 addKeyIfNotFound(XmlElement & xml,const String & key)53 static bool addKeyIfNotFound (XmlElement& xml, const String& key) 54 { 55 if (! keyFoundAndNotSequentialDuplicate (xml, key)) 56 { 57 xml.createNewChildElement ("key")->addTextElement (key); 58 return true; 59 } 60 61 return false; 62 } 63 addPlistDictionaryKey(XmlElement & xml,const String & key,const String & value)64 static void addPlistDictionaryKey (XmlElement& xml, const String& key, const String& value) 65 { 66 if (addKeyIfNotFound (xml, key)) 67 xml.createNewChildElement ("string")->addTextElement (value); 68 } 69 70 template <size_t N> addPlistDictionaryKey(XmlElement & xml,const String & key,const char (& value)[N])71 static void addPlistDictionaryKey (XmlElement& xml, const String& key, const char (&value) [N]) 72 { 73 addPlistDictionaryKey (xml, key, String { value }); 74 } 75 addPlistDictionaryKey(XmlElement & xml,const String & key,const bool value)76 static void addPlistDictionaryKey (XmlElement& xml, const String& key, const bool value) 77 { 78 if (addKeyIfNotFound (xml, key)) 79 xml.createNewChildElement (value ? "true" : "false"); 80 } 81 addPlistDictionaryKey(XmlElement & xml,const String & key,int value)82 static void addPlistDictionaryKey (XmlElement& xml, const String& key, int value) 83 { 84 if (addKeyIfNotFound (xml, key)) 85 xml.createNewChildElement ("integer")->addTextElement (String (value)); 86 } 87 88 //============================================================================== addArrayToPlist(XmlElement & dict,String arrayKey,const StringArray & arrayElements)89 static void addArrayToPlist (XmlElement& dict, String arrayKey, const StringArray& arrayElements) 90 { 91 dict.createNewChildElement ("key")->addTextElement (arrayKey); 92 auto* plistStringArray = dict.createNewChildElement ("array"); 93 94 for (auto& e : arrayElements) 95 plistStringArray->createNewChildElement ("string")->addTextElement (e); 96 } 97 write(const File & infoPlistFile) const98 void PlistOptions::write (const File& infoPlistFile) const 99 { 100 writeStreamToFile (infoPlistFile, [&] (MemoryOutputStream& mo) { write (mo); }); 101 } 102 write(MemoryOutputStream & mo) const103 void PlistOptions::write (MemoryOutputStream& mo) const 104 { 105 XmlElement::TextFormat format; 106 format.dtd = "<!DOCTYPE plist PUBLIC \"-//Apple//DTD PLIST 1.0//EN\" \"http://www.apple.com/DTDs/PropertyList-1.0.dtd\">"; 107 createXML()->writeTo (mo, format); 108 } 109 createXML() const110 std::unique_ptr<XmlElement> PlistOptions::createXML() const 111 { 112 auto plist = parseXML (plistToMerge); 113 114 if (plist == nullptr || ! plist->hasTagName ("plist")) 115 plist.reset (new XmlElement ("plist")); 116 117 auto* dict = plist->getChildByName ("dict"); 118 119 if (dict == nullptr) 120 dict = plist->createNewChildElement ("dict"); 121 122 if (microphonePermissionEnabled) 123 addPlistDictionaryKey (*dict, "NSMicrophoneUsageDescription", microphonePermissionText); 124 125 if (cameraPermissionEnabled) 126 addPlistDictionaryKey (*dict, "NSCameraUsageDescription", cameraPermissionText); 127 128 if (bluetoothPermissionEnabled) 129 addPlistDictionaryKey (*dict, "NSBluetoothAlwaysUsageDescription", bluetoothPermissionText); 130 131 if (iOS) 132 { 133 if (bluetoothPermissionEnabled) 134 addPlistDictionaryKey (*dict, "NSBluetoothPeripheralUsageDescription", bluetoothPermissionText); // needed for pre iOS 13.0 135 136 addPlistDictionaryKey (*dict, "LSRequiresIPhoneOS", true); 137 addPlistDictionaryKey (*dict, "UIViewControllerBasedStatusBarAppearance", true); 138 139 if (shouldAddStoryboardToProject) 140 addPlistDictionaryKey (*dict, "UILaunchStoryboardName", storyboardName); 141 } 142 else 143 { 144 if (sendAppleEventsPermissionEnabled) 145 addPlistDictionaryKey (*dict, "NSAppleEventsUsageDescription", sendAppleEventsPermissionText); 146 } 147 148 addPlistDictionaryKey (*dict, "CFBundleExecutable", executableName); 149 150 if (! iOS) // (NB: on iOS this causes error ITMS-90032 during publishing) 151 addPlistDictionaryKey (*dict, "CFBundleIconFile", iconFile.exists() ? iconFile.getFileName() : String()); 152 153 addPlistDictionaryKey (*dict, "CFBundleIdentifier", bundleIdentifier); 154 addPlistDictionaryKey (*dict, "CFBundleName", projectName); 155 156 // needed by NSExtension on iOS 157 addPlistDictionaryKey (*dict, "CFBundleDisplayName", projectName); 158 addPlistDictionaryKey (*dict, "CFBundlePackageType", getXcodePackageType (type)); 159 addPlistDictionaryKey (*dict, "CFBundleSignature", getXcodeBundleSignature (type)); 160 addPlistDictionaryKey (*dict, "CFBundleShortVersionString", version); 161 addPlistDictionaryKey (*dict, "CFBundleVersion", version); 162 addPlistDictionaryKey (*dict, "NSHumanReadableCopyright", companyCopyright); 163 addPlistDictionaryKey (*dict, "NSHighResolutionCapable", true); 164 165 auto replacedDocExtensions = StringArray::fromTokens (replacePreprocessorDefs (allPreprocessorDefs, 166 documentExtensions), ",", {}); 167 replacedDocExtensions.trim(); 168 replacedDocExtensions.removeEmptyStrings (true); 169 170 if (! replacedDocExtensions.isEmpty() && type != ProjectType::Target::AudioUnitv3PlugIn) 171 { 172 dict->createNewChildElement ("key")->addTextElement ("CFBundleDocumentTypes"); 173 auto* dict2 = dict->createNewChildElement ("array")->createNewChildElement ("dict"); 174 XmlElement* arrayTag = nullptr; 175 176 for (auto ex : replacedDocExtensions) 177 { 178 if (ex.startsWithChar ('.')) 179 ex = ex.substring (1); 180 181 if (arrayTag == nullptr) 182 { 183 dict2->createNewChildElement ("key")->addTextElement ("CFBundleTypeExtensions"); 184 arrayTag = dict2->createNewChildElement ("array"); 185 186 addPlistDictionaryKey (*dict2, "CFBundleTypeName", ex); 187 addPlistDictionaryKey (*dict2, "CFBundleTypeRole", "Editor"); 188 addPlistDictionaryKey (*dict2, "CFBundleTypeIconFile", "Icon"); 189 addPlistDictionaryKey (*dict2, "NSPersistentStoreTypeKey", "XML"); 190 } 191 192 arrayTag->createNewChildElement ("string")->addTextElement (ex); 193 } 194 } 195 196 if (fileSharingEnabled && type != ProjectType::Target::AudioUnitv3PlugIn) 197 addPlistDictionaryKey (*dict, "UIFileSharingEnabled", true); 198 199 if (documentBrowserEnabled) 200 addPlistDictionaryKey (*dict, "UISupportsDocumentBrowser", true); 201 202 if (iOS) 203 { 204 if (type != ProjectType::Target::AudioUnitv3PlugIn) 205 { 206 if (statusBarHidden) 207 addPlistDictionaryKey (*dict, "UIStatusBarHidden", true); 208 209 addPlistDictionaryKey (*dict, "UIRequiresFullScreen", requiresFullScreen); 210 211 addIosScreenOrientations (*dict); 212 addIosBackgroundModes (*dict); 213 } 214 215 if (type == ProjectType::Target::StandalonePlugIn && enableIAA) 216 { 217 XmlElement audioComponentsPlistKey ("key"); 218 audioComponentsPlistKey.addTextElement ("AudioComponents"); 219 220 dict->addChildElement (new XmlElement (audioComponentsPlistKey)); 221 222 XmlElement audioComponentsPlistEntry ("array"); 223 auto* audioComponentsDict = audioComponentsPlistEntry.createNewChildElement ("dict"); 224 225 addPlistDictionaryKey (*audioComponentsDict, "name", IAAPluginName); 226 addPlistDictionaryKey (*audioComponentsDict, "manufacturer", pluginManufacturerCode.substring (0, 4)); 227 addPlistDictionaryKey (*audioComponentsDict, "type", IAATypeCode); 228 addPlistDictionaryKey (*audioComponentsDict, "subtype", pluginCode.substring (0, 4)); 229 addPlistDictionaryKey (*audioComponentsDict, "version", versionAsHex); 230 231 dict->addChildElement (new XmlElement (audioComponentsPlistEntry)); 232 } 233 } 234 235 const auto extraOptions = [&]() -> Array<XmlElement> 236 { 237 if (type == ProjectType::Target::Type::AudioUnitPlugIn) 238 return createExtraAudioUnitTargetPlistOptions(); 239 240 if (type == ProjectType::Target::Type::AudioUnitv3PlugIn) 241 return createExtraAudioUnitV3TargetPlistOptions(); 242 243 return {}; 244 }(); 245 246 for (auto& e : extraOptions) 247 dict->addChildElement (new XmlElement (e)); 248 249 return plist; 250 } 251 addIosScreenOrientations(XmlElement & dict) const252 void PlistOptions::addIosScreenOrientations (XmlElement& dict) const 253 { 254 addArrayToPlist (dict, "UISupportedInterfaceOrientations", iPhoneScreenOrientations); 255 256 if (iPadScreenOrientations != iPhoneScreenOrientations) 257 addArrayToPlist (dict, "UISupportedInterfaceOrientations~ipad", iPadScreenOrientations); 258 } 259 addIosBackgroundModes(XmlElement & dict) const260 void PlistOptions::addIosBackgroundModes (XmlElement& dict) const 261 { 262 StringArray iosBackgroundModes; 263 if (backgroundAudioEnabled) iosBackgroundModes.add ("audio"); 264 if (backgroundBleEnabled) iosBackgroundModes.add ("bluetooth-central"); 265 if (pushNotificationsEnabled) iosBackgroundModes.add ("remote-notification"); 266 267 addArrayToPlist (dict, "UIBackgroundModes", iosBackgroundModes); 268 } 269 createExtraAudioUnitTargetPlistOptions() const270 Array<XmlElement> PlistOptions::createExtraAudioUnitTargetPlistOptions() const 271 { 272 XmlElement plistKey ("key"); 273 plistKey.addTextElement ("AudioComponents"); 274 275 XmlElement plistEntry ("array"); 276 auto* dict = plistEntry.createNewChildElement ("dict"); 277 278 auto truncatedCode = pluginManufacturerCode.substring (0, 4); 279 auto pluginSubType = pluginCode.substring (0, 4); 280 281 if (truncatedCode.toLowerCase() == truncatedCode) 282 { 283 throw SaveError ("AudioUnit plugin code identifiers invalid!\n\n" 284 "You have used only lower case letters in your AU plugin manufacturer identifier. " 285 "You must have at least one uppercase letter in your AU plugin manufacturer " 286 "identifier code."); 287 } 288 289 addPlistDictionaryKey (*dict, "name", pluginManufacturer + ": " + pluginName); 290 addPlistDictionaryKey (*dict, "description", pluginDescription); 291 addPlistDictionaryKey (*dict, "factoryFunction", pluginAUExportPrefix + "Factory"); 292 addPlistDictionaryKey (*dict, "manufacturer", truncatedCode); 293 addPlistDictionaryKey (*dict, "type", auMainType.removeCharacters ("'")); 294 addPlistDictionaryKey (*dict, "subtype", pluginSubType); 295 addPlistDictionaryKey (*dict, "version", versionAsHex); 296 297 if (isAuSandboxSafe) 298 { 299 addPlistDictionaryKey (*dict, "sandboxSafe", true); 300 } 301 else if (! suppressResourceUsage) 302 { 303 dict->createNewChildElement ("key")->addTextElement ("resourceUsage"); 304 auto* resourceUsageDict = dict->createNewChildElement ("dict"); 305 306 addPlistDictionaryKey (*resourceUsageDict, "network.client", true); 307 addPlistDictionaryKey (*resourceUsageDict, "temporary-exception.files.all.read-write", true); 308 } 309 310 return { plistKey, plistEntry }; 311 } 312 createExtraAudioUnitV3TargetPlistOptions() const313 Array<XmlElement> PlistOptions::createExtraAudioUnitV3TargetPlistOptions() const 314 { 315 XmlElement plistKey ("key"); 316 plistKey.addTextElement ("NSExtension"); 317 318 XmlElement plistEntry ("dict"); 319 320 addPlistDictionaryKey (plistEntry, "NSExtensionPrincipalClass", pluginAUExportPrefix + "FactoryAUv3"); 321 addPlistDictionaryKey (plistEntry, "NSExtensionPointIdentifier", "com.apple.AudioUnit-UI"); 322 plistEntry.createNewChildElement ("key")->addTextElement ("NSExtensionAttributes"); 323 324 auto* dict = plistEntry.createNewChildElement ("dict"); 325 dict->createNewChildElement ("key")->addTextElement ("AudioComponents"); 326 auto* componentArray = dict->createNewChildElement ("array"); 327 328 auto* componentDict = componentArray->createNewChildElement ("dict"); 329 330 addPlistDictionaryKey (*componentDict, "name", pluginManufacturer + ": " + pluginName); 331 addPlistDictionaryKey (*componentDict, "description", pluginDescription); 332 addPlistDictionaryKey (*componentDict, "factoryFunction", pluginAUExportPrefix + "FactoryAUv3"); 333 addPlistDictionaryKey (*componentDict, "manufacturer", pluginManufacturerCode.substring (0, 4)); 334 addPlistDictionaryKey (*componentDict, "type", auMainType.removeCharacters ("'")); 335 addPlistDictionaryKey (*componentDict, "subtype", pluginCode.substring (0, 4)); 336 addPlistDictionaryKey (*componentDict, "version", versionAsHex); 337 addPlistDictionaryKey (*componentDict, "sandboxSafe", true); 338 339 componentDict->createNewChildElement ("key")->addTextElement ("tags"); 340 auto* tagsArray = componentDict->createNewChildElement ("array"); 341 342 tagsArray->createNewChildElement ("string") 343 ->addTextElement (isPluginSynth ? "Synth" : "Effects"); 344 345 return { plistKey, plistEntry }; 346 } 347 } 348 } 349