diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h index a6789dbcfb..2a836f3bbb 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h @@ -75,6 +75,8 @@ public: iPadScreenOrientationValue (settings, Ids::iPadScreenOrientation, getUndoManager(), "portraitlandscape"), customXcodeResourceFoldersValue (settings, Ids::customXcodeResourceFolders, getUndoManager()), customXcassetsFolderValue (settings, Ids::customXcassetsFolder, getUndoManager()), + hardenedRuntimeValue (settings, Ids::hardenedRuntime, getUndoManager()), + hardenedRuntimeOptionsValue (settings, Ids::hardenedRuntimeOptions, getUndoManager(), Array(), ","), microphonePermissionNeededValue (settings, Ids::microphonePermissionNeeded, getUndoManager()), microphonePermissionsTextValue (settings, Ids::microphonePermissionsText, getUndoManager(), "This app requires audio input. If you do not have an audio interface connected it will use the built-in microphone."), @@ -111,51 +113,54 @@ public: } //============================================================================== - String getPListToMergeString() const { return customPListValue.get(); } - String getPListPrefixHeaderString() const { return pListPrefixHeaderValue.get(); } - bool isPListPreprocessEnabled() const { return pListPreprocessValue.get(); } + String getPListToMergeString() const { return customPListValue.get(); } + String getPListPrefixHeaderString() const { return pListPrefixHeaderValue.get(); } + bool isPListPreprocessEnabled() const { return pListPreprocessValue.get(); } - String getSubprojectsString() const { return subprojectsValue.get(); } + String getSubprojectsString() const { return subprojectsValue.get(); } - String getExtraFrameworksString() const { return extraFrameworksValue.get(); } - String getFrameworkSearchPathsString() const { return frameworkSearchPathsValue.get(); } - String getExtraCustomFrameworksString() const { return extraCustomFrameworksValue.get(); } - String getEmbeddedFrameworksString() const { return embeddedFrameworksValue.get(); } + String getExtraFrameworksString() const { return extraFrameworksValue.get(); } + String getFrameworkSearchPathsString() const { return frameworkSearchPathsValue.get(); } + String getExtraCustomFrameworksString() const { return extraCustomFrameworksValue.get(); } + String getEmbeddedFrameworksString() const { return embeddedFrameworksValue.get(); } - String getPostBuildScript() const { return postbuildCommandValue.get(); } - String getPreBuildScript() const { return prebuildCommandValue.get(); } + String getPostBuildScript() const { return postbuildCommandValue.get(); } + String getPreBuildScript() const { return prebuildCommandValue.get(); } - bool shouldDuplicateAppExResourcesFolder() const { return duplicateAppExResourcesFolderValue.get(); } + bool shouldDuplicateAppExResourcesFolder() const { return duplicateAppExResourcesFolderValue.get(); } - String getDeviceFamilyString() const { return iosDeviceFamilyValue.get(); } + String getDeviceFamilyString() const { return iosDeviceFamilyValue.get(); } - String getiPhoneScreenOrientationString() const { return iPhoneScreenOrientationValue.get(); } - String getiPadScreenOrientationString() const { return iPadScreenOrientationValue.get(); } + String getiPhoneScreenOrientationString() const { return iPhoneScreenOrientationValue.get(); } + String getiPadScreenOrientationString() const { return iPadScreenOrientationValue.get(); } - String getCustomResourceFoldersString() const { return customXcodeResourceFoldersValue.get().toString().replaceCharacters ("\r\n", "::"); } - String getCustomXcassetsFolderString() const { return customXcassetsFolderValue.get(); } - String getCustomLaunchStoryboardString() const { return customLaunchStoryboardValue.get(); } + String getCustomResourceFoldersString() const { return customXcodeResourceFoldersValue.get().toString().replaceCharacters ("\r\n", "::"); } + String getCustomXcassetsFolderString() const { return customXcassetsFolderValue.get(); } + String getCustomLaunchStoryboardString() const { return customLaunchStoryboardValue.get(); } + + bool isHardenedRuntimeEnabled() const { return hardenedRuntimeValue.get(); } + Array getHardenedRuntimeOptions() const { return *hardenedRuntimeOptionsValue.get().getArray(); } bool isMicrophonePermissionEnabled() const { return microphonePermissionNeededValue.get(); } String getMicrophonePermissionsTextString() const { return microphonePermissionsTextValue.get(); } - bool isCameraPermissionEnabled() const { return cameraPermissionNeededValue.get(); } - String getCameraPermissionTextString() const { return cameraPermissionTextValue.get(); } + bool isCameraPermissionEnabled() const { return cameraPermissionNeededValue.get(); } + String getCameraPermissionTextString() const { return cameraPermissionTextValue.get(); } - bool isInAppPurchasesEnabled() const { return iosInAppPurchasesValue.get(); } - bool isBackgroundAudioEnabled() const { return iosBackgroundAudioValue.get(); } - bool isBackgroundBleEnabled() const { return iosBackgroundBleValue.get(); } - bool isPushNotificationsEnabled() const { return iosPushNotificationsValue.get(); } - bool isAppGroupsEnabled() const { return iosAppGroupsValue.get(); } - bool isiCloudPermissionsEnabled() const { return iCloudPermissionsValue.get(); } - bool isFileSharingEnabled() const { return uiFileSharingEnabledValue.get(); } - bool isDocumentBrowserEnabled() const { return uiSupportsDocumentBrowserValue.get(); } - bool isStatusBarHidden() const { return uiStatusBarHiddenValue.get(); } + bool isInAppPurchasesEnabled() const { return iosInAppPurchasesValue.get(); } + bool isBackgroundAudioEnabled() const { return iosBackgroundAudioValue.get(); } + bool isBackgroundBleEnabled() const { return iosBackgroundBleValue.get(); } + bool isPushNotificationsEnabled() const { return iosPushNotificationsValue.get(); } + bool isAppGroupsEnabled() const { return iosAppGroupsValue.get(); } + bool isiCloudPermissionsEnabled() const { return iCloudPermissionsValue.get(); } + bool isFileSharingEnabled() const { return uiFileSharingEnabledValue.get(); } + bool isDocumentBrowserEnabled() const { return uiSupportsDocumentBrowserValue.get(); } + bool isStatusBarHidden() const { return uiStatusBarHiddenValue.get(); } - String getIosDevelopmentTeamIDString() const { return iosDevelopmentTeamIDValue.get(); } - String getAppGroupIdString() const { return iosAppGroupsIDValue.get(); } + String getIosDevelopmentTeamIDString() const { return iosDevelopmentTeamIDValue.get(); } + String getAppGroupIdString() const { return iosAppGroupsIDValue.get(); } - String getDefaultLaunchStoryboardName() const { jassert (iOS); return "LaunchScreen"; } + String getDefaultLaunchStoryboardName() const { jassert (iOS); return "LaunchScreen"; } //============================================================================== bool usesMMFiles() const override { return true; } @@ -263,6 +268,44 @@ public: "Using a leading '.' is optional, and the extensions are not case-sensitive."); } + if (isOSX()) + { + props.add (new ChoicePropertyComponent (hardenedRuntimeValue, "Use Hardened Runtime"), + "Enable this to use the hardened runtime required for app notarization."); + + std::vector> options + { + { "Allow Execution of JIT-compiled Code", "cs.allow-jit" }, + { "Allow Unsigned Executable Memory", "cs.allow-unsigned-executable-memory" }, + { "Allow DYLD Environment Variables", "cs.allow-dyld-environment-variables" }, + { "Disable Library Validation", "cs.disable-library-validation" }, + { "Disable Executable Memory Protection", "cs.disable-executable-page-protection" }, + { "Debugging Tool", "cs.debugger" }, + { "Audio Input", "device.audio-input" }, + { "Camera", "device.camera" }, + { "Location", "personal-information.location" }, + { "Address Book", "personal-information.addressbook" }, + { "Calendar", "personal-information.calendars" }, + { "Photos Library", "personal-information.photos-library" }, + { "Apple Events", "automation.apple-events" }, + }; + + StringArray keys; + Array values; + + for (auto& opt : options) + { + keys.add (opt.first); + values.add ("com.apple.security." + opt.second); + } + + props.add (new MultiChoicePropertyComponentWithEnablement (hardenedRuntimeOptionsValue, + hardenedRuntimeValue, + "Hardened Runtime Options", + keys, + values)); + } + props.add (new ChoicePropertyComponent (microphonePermissionNeededValue, "Microphone Access"), "Enable this to allow your app to use the microphone. " "The user of your app will be prompted to grant microphone access permissions."); @@ -969,6 +1012,7 @@ public: auto pushNotificationsEnabled = owner.isPushNotificationsEnabled() ? 1 : 0; auto sandboxEnabled = (type == Target::AudioUnitv3PlugIn ? 1 : 0); + auto hardendedRuntimeEnabled = owner.isHardenedRuntimeEnabled() ? 1 : 0; attributes << "SystemCapabilities = {"; attributes << "com.apple.ApplicationGroups.iOS = { enabled = " << appGroupsEnabled << "; }; "; @@ -976,6 +1020,7 @@ public: attributes << "com.apple.InterAppAudio = { enabled = " << interAppAudioEnabled << "; }; "; attributes << "com.apple.Push = { enabled = " << pushNotificationsEnabled << "; }; "; attributes << "com.apple.Sandbox = { enabled = " << sandboxEnabled << "; }; "; + attributes << "com.apple.HardenedRuntime = { enabled = " << hardendedRuntimeEnabled << "; }; "; if (owner.iOS && owner.isiCloudPermissionsEnabled()) attributes << "com.apple.iCloud = { enabled = 1; }; "; @@ -1019,7 +1064,10 @@ public: //============================================================================== bool shouldAddEntitlements() const { - if (owner.isPushNotificationsEnabled() || owner.isAppGroupsEnabled() || (owner.isiOS() && owner.isiCloudPermissionsEnabled())) + if (owner.isPushNotificationsEnabled() + || owner.isAppGroupsEnabled() + || owner.isHardenedRuntimeEnabled() + || (owner.isiOS() && owner.isiCloudPermissionsEnabled())) return true; if (owner.project.getProjectType().isAudioPlugin() @@ -1172,6 +1220,9 @@ public: s.set ("CONFIGURATION_BUILD_DIR", addQuotesIfRequired (configurationBuildDir)); + if (owner.isHardenedRuntimeEnabled()) + s.set ("ENABLE_HARDENED_RUNTIME", "YES"); + String gccVersion ("com.apple.compilers.llvm.clang.1_0"); if (owner.iOS) @@ -1819,6 +1870,7 @@ private: postbuildCommandValue, prebuildCommandValue, duplicateAppExResourcesFolderValue, iosDeviceFamilyValue, iPhoneScreenOrientationValue, iPadScreenOrientationValue, customXcodeResourceFoldersValue, customXcassetsFolderValue, + hardenedRuntimeValue, hardenedRuntimeOptionsValue, microphonePermissionNeededValue, microphonePermissionsTextValue, cameraPermissionNeededValue, cameraPermissionTextValue, uiFileSharingEnabledValue, uiSupportsDocumentBrowserValue, uiStatusBarHiddenValue, documentExtensionsValue, iosInAppPurchasesValue, iosBackgroundAudioValue, iosBackgroundBleValue, iosPushNotificationsValue, iosAppGroupsValue, iCloudPermissionsValue, @@ -1967,6 +2019,7 @@ private: void addFilesAndGroupsToProject (StringArray& topLevelGroupIDs) const { auto entitlements = getEntitlements(); + if (entitlements.size() > 0) topLevelGroupIDs.add (addEntitlementsFile (entitlements)); @@ -2417,7 +2470,7 @@ private: s.set ("CLANG_WARN_SUSPICIOUS_MOVE", "YES"); s.set ("CLANG_WARN_UNREACHABLE_CODE", "YES"); s.set ("CLANG_WARN__DUPLICATE_METHOD_MATCH", "YES"); - s.set ("WARNING_CFLAGS", "-Wreorder"); + s.set ("WARNING_CFLAGS", "\"-Wreorder\""); if (projectType.isStaticLibrary()) { @@ -2453,7 +2506,7 @@ private: s.set ("ZERO_LINK", "NO"); if (xcodeCanUseDwarf) - s.set ("DEBUG_INFORMATION_FORMAT", "\"dwarf\""); + s.set ("DEBUG_INFORMATION_FORMAT", "dwarf"); s.set ("PRODUCT_NAME", replacePreprocessorTokens (config, config.getTargetBinaryNameString()).quoted()); @@ -2919,6 +2972,7 @@ private: StringPairArray getEntitlements() const { StringPairArray entitlements; + if (project.getProjectType().isAudioPlugin()) { if (isiOS()) @@ -2936,12 +2990,12 @@ private: if (isPushNotificationsEnabled()) entitlements.set (isiOS() ? "aps-environment" : "com.apple.developer.aps-environment", - "development"); + "development"); } if (isAppGroupsEnabled()) { - auto appGroups = StringArray::fromTokens (getAppGroupIdString(), ";", { }); + auto appGroups = StringArray::fromTokens (getAppGroupIdString(), ";", {}); auto groups = String (""); for (auto group : appGroups) @@ -2952,6 +3006,10 @@ private: entitlements.set ("com.apple.security.application-groups", groups); } + if (isHardenedRuntimeEnabled()) + for (auto& option : getHardenedRuntimeOptions()) + entitlements.set (option, ""); + if (isiOS() && isiCloudPermissionsEnabled()) { entitlements.set ("com.apple.developer.icloud-container-identifiers", diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h index 786ec282bb..69b0be1329 100644 --- a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h +++ b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h @@ -185,6 +185,8 @@ namespace Ids DECLARE_ID (showAllCode); DECLARE_ID (useLocalCopy); DECLARE_ID (overwriteOnSave); + DECLARE_ID (hardenedRuntime); + DECLARE_ID (hardenedRuntimeOptions); DECLARE_ID (microphonePermissionNeeded); DECLARE_ID (microphonePermissionsText); DECLARE_ID (cameraPermissionNeeded); diff --git a/extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_PropertyComponentsWithEnablement.h b/extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_PropertyComponentsWithEnablement.h index 7ce4eea43d..7fb369b659 100644 --- a/extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_PropertyComponentsWithEnablement.h +++ b/extras/Projucer/Source/Utility/UI/PropertyComponents/jucer_PropertyComponentsWithEnablement.h @@ -118,3 +118,33 @@ private: setEnabled (valueWithDefault.get()); } }; + +//============================================================================== +class MultiChoicePropertyComponentWithEnablement : public MultiChoicePropertyComponent, + private Value::Listener +{ +public: + MultiChoicePropertyComponentWithEnablement (ValueWithDefault& valueToControl, + ValueWithDefault valueToListenTo, + const String& propertyName, + const StringArray& choices, + const Array& correspondingValues) + : MultiChoicePropertyComponent (valueToControl, + propertyName, + choices, + correspondingValues), + valueWithDefault (valueToListenTo), + value (valueToListenTo.getPropertyAsValue()) + { + value.addListener (this); + valueChanged (value); + } + + ~MultiChoicePropertyComponentWithEnablement() override { value.removeListener (this); } + +private: + void valueChanged (Value&) override { setEnabled (valueWithDefault.get()); } + + ValueWithDefault valueWithDefault; + Value value; +};