From ef61128127d2e4812918cc05d133725b6e86bebd Mon Sep 17 00:00:00 2001 From: attila Date: Tue, 3 Oct 2023 20:33:14 +0200 Subject: [PATCH] Projucer: Fix the type of the iokit sandbox exception in the Xcode exporter --- .../utils/juce_Entitlements.cpp | 11 ++ .../utils/juce_Entitlements.h | 1 + .../Source/Application/jucer_MainWindow.cpp | 2 - .../Projucer/Source/Project/jucer_Project.cpp | 20 +-- .../Projucer/Source/Project/jucer_Project.h | 71 +++++++++- .../ProjectSaving/jucer_ProjectExport_Xcode.h | 132 ++++++++++++++---- .../ProjectSaving/jucer_ProjectExporter.h | 1 - .../Source/Utility/Helpers/jucer_PresetIDs.h | 1 + 8 files changed, 197 insertions(+), 42 deletions(-) diff --git a/extras/Build/juce_build_tools/utils/juce_Entitlements.cpp b/extras/Build/juce_build_tools/utils/juce_Entitlements.cpp index 3696035050..5835a7e2ec 100644 --- a/extras/Build/juce_build_tools/utils/juce_Entitlements.cpp +++ b/extras/Build/juce_build_tools/utils/juce_Entitlements.cpp @@ -123,6 +123,17 @@ namespace juce:: build_tools paths += "\n\t"; entitlements.set (option.key, paths); } + + if (! appSandboxExceptionIOKit.isEmpty()) + { + String ioKitClasses = ""; + + for (const auto& c : appSandboxExceptionIOKit) + ioKitClasses += "\n\t\t" + c + ""; + + ioKitClasses += "\n\t"; + entitlements.set ("com.apple.security.temporary-exception.iokit-user-client-class", ioKitClasses); + } } } diff --git a/extras/Build/juce_build_tools/utils/juce_Entitlements.h b/extras/Build/juce_build_tools/utils/juce_Entitlements.h index dd8f60e3e1..06e86fef65 100644 --- a/extras/Build/juce_build_tools/utils/juce_Entitlements.h +++ b/extras/Build/juce_build_tools/utils/juce_Entitlements.h @@ -56,6 +56,7 @@ namespace juce::build_tools }; std::vector appSandboxTemporaryPaths; + StringArray appSandboxExceptionIOKit; private: StringPairArray getEntitlements() const; diff --git a/extras/Projucer/Source/Application/jucer_MainWindow.cpp b/extras/Projucer/Source/Application/jucer_MainWindow.cpp index 002bfa618a..d2884fafbd 100644 --- a/extras/Projucer/Source/Application/jucer_MainWindow.cpp +++ b/extras/Projucer/Source/Application/jucer_MainWindow.cpp @@ -385,8 +385,6 @@ void MainWindow::openFile (const File& file, std::function callback parent->createProjectContentCompIfNeeded(); parent->getProjectContentComponent()->reloadLastOpenDocuments(); - - parent->currentProject->updateDeprecatedProjectSettingsInteractively(); } NullCheckedInvocation::invoke (callback, saveResult); diff --git a/extras/Projucer/Source/Project/jucer_Project.cpp b/extras/Projucer/Source/Project/jucer_Project.cpp index be7c103aad..26704bf630 100644 --- a/extras/Projucer/Source/Project/jucer_Project.cpp +++ b/extras/Projucer/Source/Project/jucer_Project.cpp @@ -261,14 +261,6 @@ void Project::updateDeprecatedProjectSettings() exporter->updateDeprecatedSettings(); } -void Project::updateDeprecatedProjectSettingsInteractively() -{ - jassert (! ProjucerApplication::getApp().isRunningCommandLine); - - for (ExporterIterator exporter (*this); exporter.next();) - exporter->updateDeprecatedSettingsInteractively(); -} - void Project::initialiseMainGroup() { // Create main file group if missing @@ -462,8 +454,8 @@ void Project::removeDefunctExporters() warningMessage << "\n" << TRANS ("These exporters have been removed from the project. If you save the project they will be also erased from the .jucer file."); - auto options = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon, warningTitle, warningMessage); - messageBox = AlertWindow::showScopedAsync (options, nullptr); + exporterRemovalMessageBoxOptions = MessageBoxOptions::makeOptionsOk (MessageBoxIconType::WarningIcon, warningTitle, warningMessage); + messageBoxQueueListenerScope = messageBoxQueue.addListener (*this); } } } @@ -1169,6 +1161,14 @@ void Project::valueTreeChildAddedOrRemoved (ValueTree& parent, ValueTree& child) changed(); } +void Project::canCreateMessageBox (CreatorFunction f) +{ + messageBox = f (*exporterRemovalMessageBoxOptions, [this] (auto) + { + messageBoxQueueListenerScope.reset(); + }); +} + void Project::valueTreeChildAdded (ValueTree& parent, ValueTree& child) { valueTreeChildAddedOrRemoved (parent, child); diff --git a/extras/Projucer/Source/Project/jucer_Project.h b/extras/Projucer/Source/Project/jucer_Project.h index c9d91a4fa3..d258d34578 100644 --- a/extras/Projucer/Source/Project/jucer_Project.h +++ b/extras/Projucer/Source/Project/jucer_Project.h @@ -117,6 +117,64 @@ namespace ProjectMessages using MessageAction = std::pair>; } +// Can be shared between multiple classes wanting to create a MessageBox. Ensures that there is one +// MessageBox active at a given time. +class MessageBoxQueue : private AsyncUpdater +{ +public: + struct Listener + { + using CreatorFunction = std::function)>; + + virtual ~Listener() = default; + + virtual void canCreateMessageBox (CreatorFunction) = 0; + }; + + void handleAsyncUpdate() + { + schedule(); + } + + auto addListener (Listener& l) + { + triggerAsyncUpdate(); + return listeners.addScoped (l); + } + +private: + ScopedMessageBox create (MessageBoxOptions options, std::function callback) + { + hasActiveMessageBox = true; + + return AlertWindow::showScopedAsync (options, [this, cb = std::move (callback)] (int result) + { + cb (result); + hasActiveMessageBox = false; + triggerAsyncUpdate(); + }); + } + + void schedule() + { + if (hasActiveMessageBox) + return; + + auto& currentListeners = listeners.getListeners(); + + if (! currentListeners.isEmpty()) + { + currentListeners[0]->canCreateMessageBox ([this] (auto o, auto c) + { + return create (o, c); + }); + } + } + + ListenerList listeners; + bool hasActiveMessageBox = false; +}; + enum class Async { no, yes }; //============================================================================== @@ -124,7 +182,8 @@ class Project final : public FileBasedDocument, private ValueTree::Listener, private LicenseController::LicenseStateListener, private ChangeListener, - private AvailableModulesList::Listener + private AvailableModulesList::Listener, + private MessageBoxQueue::Listener { public: //============================================================================== @@ -355,8 +414,6 @@ public: static build_tools::ProjectType::Target::Type getTargetTypeFromFilePath (const File& file, bool returnSharedTargetIfNoValidSuffix); //============================================================================== - void updateDeprecatedProjectSettingsInteractively(); - StringPairArray getAppConfigDefs(); StringPairArray getAudioPluginFlags() const; @@ -548,6 +605,8 @@ public: bool isFileModificationCheckPending() const; bool isSaveAndExportDisabled() const; + MessageBoxQueue messageBoxQueue; + private: //============================================================================== void valueTreePropertyChanged (ValueTree&, const Identifier&) override; @@ -557,6 +616,9 @@ private: void valueTreeChildAddedOrRemoved (ValueTree&, ValueTree&); + //============================================================================== + void canCreateMessageBox (CreatorFunction) override; + //============================================================================== template static auto& getEnabledModulesImpl (This&); @@ -667,6 +729,9 @@ private: std::unique_ptr chooser; std::unique_ptr saver; + + std::optional exporterRemovalMessageBoxOptions; + ErasedScopeGuard messageBoxQueueListenerScope; ScopedMessageBox messageBox; //============================================================================== diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h index bd73fcc684..57d7d9400b 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExport_Xcode.h @@ -54,7 +54,8 @@ xcrun codesign --verify "$JUCE_FULL_PRODUCT_PATH" || xcrun codesign -f -s - "$JU )"; //============================================================================== -class XcodeProjectExporter final : public ProjectExporter +class XcodeProjectExporter final : public ProjectExporter, + private MessageBoxQueue::Listener { public: //============================================================================== @@ -97,6 +98,7 @@ public: appSandboxHomeDirRWValue (settings, Ids::appSandboxHomeDirRW, getUndoManager()), appSandboxAbsDirROValue (settings, Ids::appSandboxAbsDirRO, getUndoManager()), appSandboxAbsDirRWValue (settings, Ids::appSandboxAbsDirRW, getUndoManager()), + appSandboxExceptionIOKitValue (settings, Ids::appSandboxExceptionIOKit, getUndoManager()), hardenedRuntimeValue (settings, Ids::hardenedRuntime, getUndoManager()), hardenedRuntimeOptionsValue (settings, Ids::hardenedRuntimeOptions, getUndoManager(), Array(), ","), microphonePermissionNeededValue (settings, Ids::microphonePermissionNeeded, getUndoManager()), @@ -144,6 +146,11 @@ public: name = getDisplayNameMac(); targetLocationValue.setDefault (getDefaultBuildsRootFolder() + getTargetFolderNameMac()); } + + if (needsDisplayMessageBox()) + { + messageBoxQueueListenerScope = project.messageBoxQueue.addListener (*this); + } } static XcodeProjectExporter* createForSettings (Project& projectToUse, const ValueTree& settingsToUse) @@ -224,6 +231,11 @@ public: return result; } + StringArray getAppSandboxExceptionIOKitClasses() const + { + return getCommaOrWhitespaceSeparatedItems (appSandboxExceptionIOKitValue.get()); + } + Array getValidArchs() const { return *validArchsValue.get().getArray(); } bool isMicrophonePermissionEnabled() const { return microphonePermissionNeededValue.get(); } @@ -510,7 +522,6 @@ public: { "Temporary Exception: Audio Unit Hosting", "temporary-exception.audio-unit-host" }, { "Temporary Exception: Global Mach Service", "temporary-exception.mach-lookup.global-name" }, { "Temporary Exception: Global Mach Service Dynamic Registration", "temporary-exception.mach-register.global-name" }, - { "Temporary Exception: IOKit User Client Class", "temporary-exception.iokit-user-client-class" }, { "Temporary Exception: Shared Preference Domain (Read Only)", "temporary-exception.shared-preference.read-only" }, { "Temporary Exception: Shared Preference Domain (Read/Write)", "temporary-exception.shared-preference.read-write" } }; @@ -541,6 +552,14 @@ public: "See Apple's File Access Temporary Exceptions documentation."); } + props.add (new TextPropertyComponentWithEnablement (appSandboxExceptionIOKitValue, + appSandboxValue, + "App sandbox temporary exception: additional IOUserClient subclasses", + 8192, + true), + "A list of IOUserClient subclasses to open or to set properties on. " + "See Apple's IOKit User Client Class Temporary Exception documentation."); + props.add (new ChoicePropertyComponent (hardenedRuntimeValue, "Use Hardened Runtime"), "Enable this to use the hardened runtime required for app notarization."); @@ -781,32 +800,30 @@ public: updateOldOrientationSettings(); } - void updateDeprecatedSettingsInteractively() override - { - if (hasInvalidPostBuildScript()) - { - String alertWindowText = iOS ? "Your Xcode (iOS) Exporter settings use an invalid post-build script. Click 'Update' to remove it." - : "Your Xcode (macOS) Exporter settings use a pre-JUCE 4.2 post-build script to move the plug-in binaries to their plug-in install folders.\n\n" - "Since JUCE 4.2, this is instead done using \"AU/VST/VST2/AAX Binary Location\" in the Xcode (OS X) configuration settings.\n\n" - "Click 'Update' to remove the script (otherwise your plug-in may not compile correctly)."; - - auto options = MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType::WarningIcon, - "Project settings: " + project.getDocumentTitle(), - alertWindowText, - "Update", - "Cancel"); - messageBox = AlertWindow::showScopedAsync (options, [this] (int result) - { - if (result != 0) - postbuildCommandValue.resetToDefault(); - }); - } - } - bool hasInvalidPostBuildScript() const { // check whether the script is identical to the old one that the Introjucer used to auto-generate - return (MD5 (getPostBuildScript().toUTF8()).toHexString() == "265ac212a7e734c5bbd6150e1eae18a1"); + return ! userAcknowledgedInvalidPostBuildScript + && (MD5 (getPostBuildScript().toUTF8()).toHexString() == "265ac212a7e734c5bbd6150e1eae18a1"); + } + + bool hasDefunctIOKitSetting() const + { + auto v = appSandboxOptionsValue.get(); + + if (! v.isArray()) + { + jassertfalse; + return false; + } + + return ! userAcknowledgedDefunctIOKitSetting + && v.getArray()->contains ("com.apple.security.temporary-exception.iokit-user-client-class"); + } + + bool needsDisplayMessageBox() const + { + return hasInvalidPostBuildScript() || hasDefunctIOKitSetting(); } //============================================================================== @@ -2082,6 +2099,62 @@ private: File getProjectBundle() const { return getTargetFolder().getChildFile (project.getProjectFilenameRootString()).withFileExtension (".xcodeproj"); } + void canCreateMessageBox (CreatorFunction f) override + { + if (hasInvalidPostBuildScript()) + { + String alertWindowText = iOS ? "Your Xcode (iOS) Exporter settings use an invalid post-build script. Click 'Update' to remove it." + : "Your Xcode (macOS) Exporter settings use a pre-JUCE 4.2 post-build script to move the plug-in binaries to their plug-in install folders.\n\n" + "Since JUCE 4.2, this is instead done using \"AU/VST/VST2/AAX Binary Location\" in the Xcode (OS X) configuration settings.\n\n" + "Click 'Update' to remove the script (otherwise your plug-in may not compile correctly)."; + + auto options = MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType::WarningIcon, + "Project settings: " + project.getDocumentTitle(), + alertWindowText, + "Update", + "Cancel"); + + messageBox = f (options, [this] (int result) + { + userAcknowledgedInvalidPostBuildScript = true; + + if (result != 0) + postbuildCommandValue.resetToDefault(); + + if (! needsDisplayMessageBox()) + messageBoxQueueListenerScope.reset(); + }); + } + else if (hasDefunctIOKitSetting()) + { + String alertWindowText = "Your Xcode (macOS) Exporter settings use a defunct, boolean value for the iokit-user-client-class temporary exception entitlement.\n\n" + "If you need this entitlement, add the IOUserClient subclasses to the new IOKit exception related field.\n\n" + "For more information see Apple's IOKit User Client Class Temporary Exception documentation.\n\n" + "Clicking 'Update' will remove the defunct setting from your project."; + + auto options = MessageBoxOptions::makeOptionsOkCancel (MessageBoxIconType::WarningIcon, + "Project settings: " + project.getDocumentTitle(), + alertWindowText, + "Update", + "Cancel"); + + messageBox = f (std::move (options), [this] (int result) + { + userAcknowledgedDefunctIOKitSetting = true; + + if (result != 0) + { + auto v = appSandboxOptionsValue.get(); + v.getArray()->removeAllInstancesOf ("com.apple.security.temporary-exception.iokit-user-client-class"); + appSandboxOptionsValue.setValue (v, nullptr); + } + + if (! needsDisplayMessageBox()) + messageBoxQueueListenerScope.reset(); + }); + } + } + //============================================================================== void createObjects() const { @@ -3267,6 +3340,7 @@ private: options.hardenedRuntimeOptions = getHardenedRuntimeOptions(); options.appSandboxOptions = getAppSandboxOptions(); options.appSandboxTemporaryPaths = getAppSandboxTemporaryPaths(); + options.appSandboxExceptionIOKit = getAppSandboxExceptionIOKitClasses(); const auto entitlementsFile = getTargetFolder().getChildFile (target.getEntitlementsFilename()); build_tools::overwriteFileIfDifferentOrThrow (entitlementsFile, options.getEntitlementsFileContent()); @@ -3748,6 +3822,7 @@ private: iPadScreenOrientationValue, customXcodeResourceFoldersValue, customXcassetsFolderValue, appSandboxValue, appSandboxInheritanceValue, appSandboxOptionsValue, appSandboxHomeDirROValue, appSandboxHomeDirRWValue, appSandboxAbsDirROValue, appSandboxAbsDirRWValue, + appSandboxExceptionIOKitValue, hardenedRuntimeValue, hardenedRuntimeOptionsValue, microphonePermissionNeededValue, microphonePermissionsTextValue, cameraPermissionNeededValue, cameraPermissionTextValue, @@ -3757,7 +3832,6 @@ private: iosContentSharingValue, iosBackgroundAudioValue, iosBackgroundBleValue, iosPushNotificationsValue, iosAppGroupsValue, iCloudPermissionsValue, networkingMulticastValue, iosDevelopmentTeamIDValue, iosAppGroupsIDValue, keepCustomXcodeSchemesValue, useHeaderMapValue, customLaunchStoryboardValue, exporterBundleIdentifierValue, suppressPlistResourceUsageValue, useLegacyBuildSystemValue, buildNumber; - ScopedMessageBox messageBox; struct SandboxFileAccessProperty { @@ -3773,5 +3847,11 @@ private: { appSandboxAbsDirRWValue, "App sandbox temporary exception: absolute path read/write file access", "absolute-path.read-write" } }; + bool userAcknowledgedInvalidPostBuildScript = false; + bool userAcknowledgedDefunctIOKitSetting = false; + + ErasedScopeGuard messageBoxQueueListenerScope; + ScopedMessageBox messageBox; + JUCE_DECLARE_NON_COPYABLE (XcodeProjectExporter) }; diff --git a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h index c216d4b240..5a5369df79 100644 --- a/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h +++ b/extras/Projucer/Source/ProjectSaving/jucer_ProjectExporter.h @@ -101,7 +101,6 @@ public: virtual bool canCopeWithDuplicateFiles() = 0; virtual bool supportsUserDefinedConfigurations() const = 0; // false if exporter only supports two configs Debug and Release virtual void updateDeprecatedSettings() {} - virtual void updateDeprecatedSettingsInteractively() {} virtual void initialiseDependencyPathValues() {} // IDE targeted by exporter diff --git a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h index a95cf1c364..0214d983b9 100644 --- a/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h +++ b/extras/Projucer/Source/Utility/Helpers/jucer_PresetIDs.h @@ -202,6 +202,7 @@ namespace Ids DECLARE_ID (appSandboxHomeDirRW); DECLARE_ID (appSandboxAbsDirRO); DECLARE_ID (appSandboxAbsDirRW); + DECLARE_ID (appSandboxExceptionIOKit); DECLARE_ID (hardenedRuntime); DECLARE_ID (hardenedRuntimeOptions); DECLARE_ID (microphonePermissionNeeded);