From f70524cda35e6d1ef099d4d8bc700abd57d72a92 Mon Sep 17 00:00:00 2001 From: Austin Smith Date: Sun, 9 Aug 2026 01:27:43 -0700 Subject: [PATCH] add mac app store distribution target --- BitDream.xcodeproj/project.pbxproj | 318 +++++++++++++++++- .../xcschemes/BitDreamAppStore.xcscheme | 78 +++++ BitDream/AppUpdater.swift | 2 +- BitDream/BitDreamApp.swift | 18 +- BitDream/Info-AppStore.plist | 68 ++++ BitDream/PreviewSupport/PreviewSupport.swift | 13 +- .../Settings/macOSGeneralSettingsTab.swift | 22 +- BitDream/Views/macOS/macOSMenuCommands.swift | 10 +- docs/build-settings.md | 5 + docs/mac-app-store.md | 50 +++ docs/releases.md | 3 + 11 files changed, 564 insertions(+), 23 deletions(-) create mode 100644 BitDream.xcodeproj/xcshareddata/xcschemes/BitDreamAppStore.xcscheme create mode 100644 BitDream/Info-AppStore.plist create mode 100644 docs/mac-app-store.md diff --git a/BitDream.xcodeproj/project.pbxproj b/BitDream.xcodeproj/project.pbxproj index 3a4e51a..6bf020b 100644 --- a/BitDream.xcodeproj/project.pbxproj +++ b/BitDream.xcodeproj/project.pbxproj @@ -14,6 +14,12 @@ 4BDACD192E73DB220074F714 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BDACD182E73DB220074F714 /* SwiftUI.framework */; }; 4BDACD2A2E73DB230074F714 /* BitDreamWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 4BDACD142E73DB220074F714 /* BitDreamWidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; 4D1000013B00000000B1C2D3 /* Sparkle in Frameworks */ = {isa = PBXBuildFile; platformFilters = (macos, ); productRef = 4D1000073B00000000B1C2D3 /* Sparkle */; }; + 6A20000B4200000000B1C2D3 /* BitDreamWidgetsExtension.appex in Embed Foundation Extensions */ = {isa = PBXBuildFile; fileRef = 6A20000E4200000000B1C2D3 /* BitDreamWidgetsExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; }; + 6A2000174200000000B1C2D3 /* BitDream/Widgets/AppGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BDACD502E73DBBE0074F714 /* BitDream/Widgets/AppGroup.swift */; }; + 6A2000184200000000B1C2D3 /* BitDream/Widgets/DataModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BCF274D2E774EDC00F6BF76 /* BitDream/Widgets/DataModels.swift */; }; + 6A2000194200000000B1C2D3 /* BitDream/Widgets/WidgetKitBridge.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4BD855492E77424B00606224 /* BitDream/Widgets/WidgetKitBridge.swift */; }; + 6A20001A4200000000B1C2D3 /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BDACD162E73DB220074F714 /* WidgetKit.framework */; }; + 6A20001B4200000000B1C2D3 /* SwiftUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 4BDACD182E73DB220074F714 /* SwiftUI.framework */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -31,6 +37,13 @@ remoteGlobalIDString = 4B1DADDA295E6C440037E9FB; remoteInfo = BitDream; }; + 6A20000C4200000000B1C2D3 /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 4B1DADD3295E6C440037E9FB /* Project object */; + proxyType = 1; + remoteGlobalIDString = 6A2000134200000000B1C2D3; + remoteInfo = BitDreamWidgetsAppStoreExtension; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -43,6 +56,15 @@ ); name = "Embed Foundation Extensions"; }; + 6A20000A4200000000B1C2D3 /* Embed Foundation Extensions */ = { + isa = PBXCopyFilesBuildPhase; + dstPath = ""; + dstSubfolder = PlugIns; + files = ( + 6A20000B4200000000B1C2D3 /* BitDreamWidgetsExtension.appex in Embed Foundation Extensions */, + ); + name = "Embed Foundation Extensions"; + }; /* End PBXCopyFilesBuildPhase section */ /* Begin PBXFileReference section */ @@ -56,6 +78,8 @@ 4D2000013B10000000C3D4E5 /* BuildSettings.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = BuildSettings.xcconfig; sourceTree = ""; }; 4D2000023B10000000C3D4E5 /* BuildSettings.example.local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = BuildSettings.example.local.xcconfig; sourceTree = ""; }; 5A1000074100000000A1B2C3 /* BitDreamTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BitDreamTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A2000014200000000B1C2D3 /* BitDream.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BitDream.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 6A20000E4200000000B1C2D3 /* BitDreamWidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = BitDreamWidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; /* End PBXFileReference section */ /* Begin PBXFileSystemSynchronizedBuildFileExceptionSet section */ @@ -69,6 +93,7 @@ 4C9C00013F10000000B1C2D3 /* Exceptions for "BitDream" folder in "BitDream" target */ = { isa = PBXFileSystemSynchronizedBuildFileExceptionSet; membershipExceptions = ( + "Info-AppStore.plist", Info.plist, "Transmission/TransmissionRPC/Examples/session-get.response.json", "Transmission/TransmissionRPC/Examples/session-stats.response.json", @@ -78,6 +103,26 @@ ); target = 4B1DADDA295E6C440037E9FB /* BitDream */; }; + 6A2000024200000000B1C2D3 /* Exceptions for "BitDream" folder in "BitDreamAppStore" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + "Info-AppStore.plist", + Info.plist, + "Transmission/TransmissionRPC/Examples/session-get.response.json", + "Transmission/TransmissionRPC/Examples/session-stats.response.json", + "Transmission/TransmissionRPC/Examples/torrent-get.response.json", + "Transmission/TransmissionRPC/Transmission-RPC-Spec.md", + Widgets/README.md, + ); + target = 6A2000064200000000B1C2D3 /* BitDreamAppStore */; + }; + 6A20000F4200000000B1C2D3 /* Exceptions for "BitDreamWidgets" folder in "BitDreamWidgetsAppStoreExtension" target */ = { + isa = PBXFileSystemSynchronizedBuildFileExceptionSet; + membershipExceptions = ( + Info.plist, + ); + target = 6A2000134200000000B1C2D3 /* BitDreamWidgetsAppStoreExtension */; + }; /* End PBXFileSystemSynchronizedBuildFileExceptionSet section */ /* Begin PBXFileSystemSynchronizedRootGroup section */ @@ -85,6 +130,7 @@ isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( 4C9C00013F10000000B1C2D3 /* Exceptions for "BitDream" folder in "BitDream" target */, + 6A2000024200000000B1C2D3 /* Exceptions for "BitDream" folder in "BitDreamAppStore" target */, ); path = BitDream; sourceTree = ""; @@ -98,6 +144,7 @@ isa = PBXFileSystemSynchronizedRootGroup; exceptions = ( 4BDACD2B2E73DB230074F714 /* Exceptions for "BitDreamWidgets" folder in "BitDreamWidgetsExtension" target */, + 6A20000F4200000000B1C2D3 /* Exceptions for "BitDreamWidgets" folder in "BitDreamWidgetsAppStoreExtension" target */, ); path = BitDreamWidgets; sourceTree = ""; @@ -128,6 +175,18 @@ files = ( ); }; + 6A2000044200000000B1C2D3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + files = ( + ); + }; + 6A2000114200000000B1C2D3 /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + files = ( + 6A20001B4200000000B1C2D3 /* SwiftUI.framework in Frameworks */, + 6A20001A4200000000B1C2D3 /* WidgetKit.framework in Frameworks */, + ); + }; /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ @@ -151,6 +210,8 @@ 4B1DADDB295E6C440037E9FB /* BitDream.app */, 5A1000074100000000A1B2C3 /* BitDreamTests.xctest */, 4BDACD142E73DB220074F714 /* BitDreamWidgetsExtension.appex */, + 6A2000014200000000B1C2D3 /* BitDream.app */, + 6A20000E4200000000B1C2D3 /* BitDreamWidgetsExtension.appex */, ); name = Products; sourceTree = ""; @@ -251,6 +312,49 @@ productReference = 5A1000074100000000A1B2C3 /* BitDreamTests.xctest */; productType = "com.apple.product-type.bundle.unit-test"; }; + 6A2000064200000000B1C2D3 /* BitDreamAppStore */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6A2000094200000000B1C2D3 /* Build configuration list for PBXNativeTarget "BitDreamAppStore" */; + buildPhases = ( + 6A2000034200000000B1C2D3 /* Sources */, + 6A2000044200000000B1C2D3 /* Frameworks */, + 6A2000054200000000B1C2D3 /* Resources */, + 6A20000A4200000000B1C2D3 /* Embed Foundation Extensions */, + ); + buildRules = ( + ); + dependencies = ( + 6A20000D4200000000B1C2D3 /* PBXTargetDependency */, + ); + fileSystemSynchronizedGroups = ( + 4B1DADDD295E6C440037E9FB /* BitDream */, + 4B4E4C1A2E8A1F6E00418FD7 /* AppIcons */, + ); + name = BitDreamAppStore; + packageProductDependencies = ( + ); + productName = BitDreamAppStore; + productReference = 6A2000014200000000B1C2D3 /* BitDream.app */; + productType = "com.apple.product-type.application"; + }; + 6A2000134200000000B1C2D3 /* BitDreamWidgetsAppStoreExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 6A2000164200000000B1C2D3 /* Build configuration list for PBXNativeTarget "BitDreamWidgetsAppStoreExtension" */; + buildPhases = ( + 6A2000104200000000B1C2D3 /* Sources */, + 6A2000114200000000B1C2D3 /* Frameworks */, + 6A2000124200000000B1C2D3 /* Resources */, + ); + buildRules = ( + ); + fileSystemSynchronizedGroups = ( + 4BDACD1A2E73DB220074F714 /* BitDreamWidgets */, + ); + name = BitDreamWidgetsAppStoreExtension; + productName = BitDreamWidgetsExtension; + productReference = 6A20000E4200000000B1C2D3 /* BitDreamWidgetsExtension.appex */; + productType = "com.apple.product-type.app-extension"; + }; /* End PBXNativeTarget section */ /* Begin PBXProject section */ @@ -271,6 +375,12 @@ CreatedOnToolsVersion = 16.4; TestTargetID = 4B1DADDA295E6C440037E9FB; }; + 6A2000064200000000B1C2D3 = { + CreatedOnToolsVersion = 26.3; + }; + 6A2000134200000000B1C2D3 = { + CreatedOnToolsVersion = 26.3; + }; }; }; buildConfigurationList = 4B1DADD6295E6C440037E9FB /* Build configuration list for PBXProject "BitDream" */; @@ -290,8 +400,10 @@ projectRoot = ""; targets = ( 4B1DADDA295E6C440037E9FB /* BitDream */, + 6A2000064200000000B1C2D3 /* BitDreamAppStore */, 5A10000F4100000000A1B2C3 /* BitDreamTests */, 4BDACD132E73DB220074F714 /* BitDreamWidgetsExtension */, + 6A2000134200000000B1C2D3 /* BitDreamWidgetsAppStoreExtension */, ); }; /* End PBXProject section */ @@ -312,6 +424,16 @@ files = ( ); }; + 6A2000054200000000B1C2D3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + files = ( + ); + }; + 6A2000124200000000B1C2D3 /* Resources */ = { + isa = PBXResourcesBuildPhase; + files = ( + ); + }; /* End PBXResourcesBuildPhase section */ /* Begin PBXSourcesBuildPhase section */ @@ -333,6 +455,19 @@ files = ( ); }; + 6A2000034200000000B1C2D3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + files = ( + ); + }; + 6A2000104200000000B1C2D3 /* Sources */ = { + isa = PBXSourcesBuildPhase; + files = ( + 6A2000174200000000B1C2D3 /* BitDream/Widgets/AppGroup.swift in Sources */, + 6A2000194200000000B1C2D3 /* BitDream/Widgets/WidgetKitBridge.swift in Sources */, + 6A2000184200000000B1C2D3 /* BitDream/Widgets/DataModels.swift in Sources */, + ); + }; /* End PBXSourcesBuildPhase section */ /* Begin PBXTargetDependency section */ @@ -346,6 +481,11 @@ target = 4B1DADDA295E6C440037E9FB /* BitDream */; targetProxy = 5A1000044100000000A1B2C3 /* PBXContainerItemProxy */; }; + 6A20000D4200000000B1C2D3 /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 6A2000134200000000B1C2D3 /* BitDreamWidgetsAppStoreExtension */; + targetProxy = 6A20000C4200000000B1C2D3 /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin XCBuildConfiguration section */ @@ -502,7 +642,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 26.0; - MARKETING_VERSION = 2.0.2; + MARKETING_VERSION = 2.0.3; PRODUCT_BUNDLE_IDENTIFIER = com.crapshack.BitDream; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -552,7 +692,7 @@ LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; MACOSX_DEPLOYMENT_TARGET = 26.0; - MARKETING_VERSION = 2.0.2; + MARKETING_VERSION = 2.0.3; PRODUCT_BUNDLE_IDENTIFIER = com.crapshack.BitDream; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "BitDream App Developer ID"; @@ -590,7 +730,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 26.0; - MARKETING_VERSION = 2.0.2; + MARKETING_VERSION = 2.0.3; PRODUCT_BUNDLE_IDENTIFIER = com.crapshack.BitDream.BitDreamWidgets; PRODUCT_NAME = "$(TARGET_NAME)"; REGISTER_APP_GROUPS = YES; @@ -636,7 +776,7 @@ ); LOCALIZATION_PREFERS_STRING_CATALOGS = YES; MACOSX_DEPLOYMENT_TARGET = 26.0; - MARKETING_VERSION = 2.0.2; + MARKETING_VERSION = 2.0.3; PRODUCT_BUNDLE_IDENTIFIER = com.crapshack.BitDream.BitDreamWidgets; PRODUCT_NAME = "$(TARGET_NAME)"; "PROVISIONING_PROFILE_SPECIFIER[sdk=macosx*]" = "BitDream Widget Developer ID"; @@ -706,6 +846,160 @@ }; name = Release; }; + 6A2000074200000000B1C2D3 /* Debug configuration for PBXNativeTarget "BitDreamAppStore" */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = BitDreamAppIconDefault; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_ENTITLEMENTS = BitDream/BitDream.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_ASSET_PATHS = "\"BitDream/Preview Content\""; + DEVELOPMENT_TEAM = T778QMSML9; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "BitDream/Info-AppStore.plist"; + INFOPLIST_KEY_CFBundleDisplayName = BitDream; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 2.0.3; + PRODUCT_BUNDLE_IDENTIFIER = com.crapshack.BitDream; + PRODUCT_NAME = BitDream; + REGISTER_APP_GROUPS = YES; + SDKROOT = macosx; + SUPPORTED_PLATFORMS = macosx; + SUPPORTS_MACCATALYST = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 6.0; + }; + name = Debug; + }; + 6A2000084200000000B1C2D3 /* Release configuration for PBXNativeTarget "BitDreamAppStore" */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_APPICON_NAME = BitDreamAppIconDefault; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_INCLUDE_ALL_APPICON_ASSETS = YES; + CODE_SIGN_ENTITLEMENTS = BitDream/BitDream.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEAD_CODE_STRIPPING = YES; + DEVELOPMENT_ASSET_PATHS = "\"BitDream/Preview Content\""; + DEVELOPMENT_TEAM = T778QMSML9; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + ENABLE_PREVIEWS = YES; + ENABLE_USER_SELECTED_FILES = readonly; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = "BitDream/Info-AppStore.plist"; + INFOPLIST_KEY_CFBundleDisplayName = BitDream; + INFOPLIST_KEY_LSApplicationCategoryType = "public.app-category.utilities"; + LD_RUNPATH_SEARCH_PATHS = "@executable_path/../Frameworks"; + MACOSX_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 2.0.3; + PRODUCT_BUNDLE_IDENTIFIER = com.crapshack.BitDream; + PRODUCT_NAME = BitDream; + REGISTER_APP_GROUPS = YES; + SDKROOT = macosx; + SUPPORTED_PLATFORMS = macosx; + SUPPORTS_MACCATALYST = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 6.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; + 6A2000144200000000B1C2D3 /* Debug configuration for PBXNativeTarget "BitDreamWidgetsAppStoreExtension" */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = BitDreamWidgets/BitDreamWidgetsExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T778QMSML9; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = BitDreamWidgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = BitDreamWidgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 2.0.3; + PRODUCT_BUNDLE_IDENTIFIER = com.crapshack.BitDream.BitDreamWidgets; + PRODUCT_NAME = BitDreamWidgetsExtension; + REGISTER_APP_GROUPS = YES; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = macosx; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_ACTIVE_COMPILATION_CONDITIONS = "DEBUG $(inherited)"; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 6.0; + }; + name = Debug; + }; + 6A2000154200000000B1C2D3 /* Release configuration for PBXNativeTarget "BitDreamWidgetsAppStoreExtension" */ = { + isa = XCBuildConfiguration; + buildSettings = { + ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES; + ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + ASSETCATALOG_COMPILER_WIDGET_BACKGROUND_COLOR_NAME = WidgetBackground; + CODE_SIGN_ENTITLEMENTS = BitDreamWidgets/BitDreamWidgetsExtension.entitlements; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + DEVELOPMENT_TEAM = T778QMSML9; + ENABLE_APP_SANDBOX = YES; + ENABLE_HARDENED_RUNTIME = YES; + ENABLE_OUTGOING_NETWORK_CONNECTIONS = YES; + GCC_C_LANGUAGE_STANDARD = gnu17; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = BitDreamWidgets/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = BitDreamWidgets; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + ); + LOCALIZATION_PREFERS_STRING_CATALOGS = YES; + MACOSX_DEPLOYMENT_TARGET = 26.0; + MARKETING_VERSION = 2.0.3; + PRODUCT_BUNDLE_IDENTIFIER = com.crapshack.BitDream.BitDreamWidgets; + PRODUCT_NAME = BitDreamWidgetsExtension; + REGISTER_APP_GROUPS = YES; + SDKROOT = macosx; + SKIP_INSTALL = YES; + SUPPORTED_PLATFORMS = macosx; + SUPPORTS_MACCATALYST = NO; + SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; + SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 6.0; + VALIDATE_PRODUCT = YES; + }; + name = Release; + }; /* End XCBuildConfiguration section */ /* Begin XCConfigurationList section */ @@ -741,6 +1035,22 @@ ); defaultConfigurationName = Release; }; + 6A2000094200000000B1C2D3 /* Build configuration list for PBXNativeTarget "BitDreamAppStore" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6A2000074200000000B1C2D3 /* Debug configuration for PBXNativeTarget "BitDreamAppStore" */, + 6A2000084200000000B1C2D3 /* Release configuration for PBXNativeTarget "BitDreamAppStore" */, + ); + defaultConfigurationName = Release; + }; + 6A2000164200000000B1C2D3 /* Build configuration list for PBXNativeTarget "BitDreamWidgetsAppStoreExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 6A2000144200000000B1C2D3 /* Debug configuration for PBXNativeTarget "BitDreamWidgetsAppStoreExtension" */, + 6A2000154200000000B1C2D3 /* Release configuration for PBXNativeTarget "BitDreamWidgetsAppStoreExtension" */, + ); + defaultConfigurationName = Release; + }; /* End XCConfigurationList section */ /* Begin XCRemoteSwiftPackageReference section */ diff --git a/BitDream.xcodeproj/xcshareddata/xcschemes/BitDreamAppStore.xcscheme b/BitDream.xcodeproj/xcshareddata/xcschemes/BitDreamAppStore.xcscheme new file mode 100644 index 0000000..f785c33 --- /dev/null +++ b/BitDream.xcodeproj/xcshareddata/xcschemes/BitDreamAppStore.xcscheme @@ -0,0 +1,78 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/BitDream/AppUpdater.swift b/BitDream/AppUpdater.swift index f2674de..53041cc 100644 --- a/BitDream/AppUpdater.swift +++ b/BitDream/AppUpdater.swift @@ -1,4 +1,4 @@ -#if os(macOS) +#if os(macOS) && canImport(Sparkle) import Foundation import Sparkle diff --git a/BitDream/BitDreamApp.swift b/BitDream/BitDreamApp.swift index 8fde97b..4238ef1 100644 --- a/BitDream/BitDreamApp.swift +++ b/BitDream/BitDreamApp.swift @@ -19,7 +19,9 @@ struct BitDreamApp: App { @NSApplicationDelegateAdaptor(AppFileOpenDelegate.self) private var appFileOpenDelegate @StateObject private var menuBarStatusItemController = MenuBarStatusItemBridge() @StateObject private var dockBadgeController = DockBadgeController() + #if canImport(Sparkle) @StateObject private var appUpdater = AppUpdater() + #endif @StateObject private var serverEditingCoordinator = MacOSServerEditingCoordinator() #endif @@ -119,7 +121,9 @@ private extension BitDreamApp { ensureStartupConnectionBehaviorApplied(store: store, modelContext: persistenceController.container.mainContext) syncMenuBarStatusItem() dockBadgeController.configure(store: store) + #if canImport(Sparkle) appUpdater.start() + #endif } .onChange(of: menuBarTransferWidgetEnabled) { _, isEnabled in syncMenuBarStatusItem(isEnabled: isEnabled) @@ -230,7 +234,11 @@ private extension BitDreamApp { } .windowResizability(.contentSize) .commands { + #if canImport(Sparkle) AppCommands(appUpdater: appUpdater) + #else + AppCommands() + #endif CommandGroup(replacing: .newItem) { } FileCommands(store: store) SearchCommands(store: store) @@ -306,9 +314,15 @@ private extension BitDreamApp { var settingsScene: some Scene { Settings { - SettingsView(store: store) // Use the same store instance + Group { + #if canImport(Sparkle) + SettingsView(store: store) + .environmentObject(appUpdater) + #else + SettingsView(store: store) + #endif + } .frame(minWidth: 500, idealWidth: 550, maxWidth: 650) - .environmentObject(appUpdater) .environmentObject(themeManager) // Pass the ThemeManager to the Settings view .immediateTheme(manager: themeManager) } diff --git a/BitDream/Info-AppStore.plist b/BitDream/Info-AppStore.plist new file mode 100644 index 0000000..7ef8cbb --- /dev/null +++ b/BitDream/Info-AppStore.plist @@ -0,0 +1,68 @@ + + + + + CFBundleDocumentTypes + + + CFBundleTypeName + BitTorrent File + LSItemContentTypes + + org.bittorrent.torrent + + CFBundleTypeRole + Viewer + LSHandlerRank + Owner + + + CFBundleURLTypes + + + CFBundleTypeRole + Viewer + CFBundleURLName + BitDream Magnet + CFBundleURLSchemes + + magnet + + + + CFBundleTypeRole + Viewer + CFBundleURLName + BitDream + CFBundleURLSchemes + + bitdream + + + + LSSupportsOpeningDocumentsInPlace + + UTExportedTypeDeclarations + + + UTTypeIdentifier + org.bittorrent.torrent + UTTypeDescription + BitTorrent file + UTTypeConformsTo + + public.data + + UTTypeTagSpecification + + public.filename-extension + + torrent + + public.mime-type + application/x-bittorrent + + + + + diff --git a/BitDream/PreviewSupport/PreviewSupport.swift b/BitDream/PreviewSupport/PreviewSupport.swift index 8e2a68c..10edcc3 100644 --- a/BitDream/PreviewSupport/PreviewSupport.swift +++ b/BitDream/PreviewSupport/PreviewSupport.swift @@ -23,8 +23,10 @@ final class PreviewEnvironment { #if os(iOS) let appIconManager: AppIconManager #endif - #if os(macOS) + #if os(macOS) && canImport(Sparkle) let appUpdater: AppUpdater + #endif + #if os(macOS) let serverEditingCoordinator: MacOSServerEditingCoordinator #endif @@ -47,8 +49,10 @@ final class PreviewEnvironment { #if os(iOS) self.appIconManager = AppIconManager.inert() #endif - #if os(macOS) + #if os(macOS) && canImport(Sparkle) self.appUpdater = AppUpdater(updatesEnabled: false) + #endif + #if os(macOS) self.serverEditingCoordinator = MacOSServerEditingCoordinator() #endif } @@ -83,10 +87,13 @@ struct PreviewContainer: View { @ViewBuilder private var configuredContent: some View { - #if os(macOS) + #if os(macOS) && canImport(Sparkle) content(previewEnvironment) .environmentObject(previewEnvironment.appUpdater) .environmentObject(previewEnvironment.serverEditingCoordinator) + #elseif os(macOS) + content(previewEnvironment) + .environmentObject(previewEnvironment.serverEditingCoordinator) #elseif os(iOS) content(previewEnvironment) .environmentObject(previewEnvironment.appIconManager) diff --git a/BitDream/Views/macOS/Settings/macOSGeneralSettingsTab.swift b/BitDream/Views/macOS/Settings/macOSGeneralSettingsTab.swift index da46ed8..a5a3c2e 100644 --- a/BitDream/Views/macOS/Settings/macOSGeneralSettingsTab.swift +++ b/BitDream/Views/macOS/Settings/macOSGeneralSettingsTab.swift @@ -4,7 +4,9 @@ import Foundation #if os(macOS) struct macOSGeneralSettingsTab: View { @Environment(\.appUserDefaults) private var userDefaults + #if canImport(Sparkle) @EnvironmentObject private var appUpdater: AppUpdater + #endif @EnvironmentObject private var themeManager: ThemeManager @ObservedObject var store: TransmissionStore @@ -24,7 +26,8 @@ struct macOSGeneralSettingsTab: View { ) } - private var automaticallyChecksForUpdatesBinding: Binding { + #if canImport(Sparkle) + private var automaticallyChecksForUpdates: Binding { Binding( get: { appUpdater.automaticallyChecksForUpdates }, set: { appUpdater.automaticallyChecksForUpdates = $0 } @@ -35,6 +38,7 @@ struct macOSGeneralSettingsTab: View { guard let date = appUpdater.lastUpdateCheckDate else { return "Never" } return date.formatted(Date.FormatStyle(date: .abbreviated, time: .shortened)) } + #endif var body: some View { VStack(alignment: .leading, spacing: 20) { @@ -150,15 +154,13 @@ struct macOSGeneralSettingsTab: View { } } + #if canImport(Sparkle) divider - settingsSection("Updates") { - Toggle("Automatically check for updates", isOn: automaticallyChecksForUpdatesBinding) + Toggle("Automatically check for updates", isOn: automaticallyChecksForUpdates) HStack(alignment: .firstTextBaseline) { - Button("Check for Updates…") { - appUpdater.checkForUpdates() - } + Button("Check for Updates…", action: appUpdater.checkForUpdates) .disabled(!appUpdater.canCheckForUpdates) Spacer() @@ -168,6 +170,7 @@ struct macOSGeneralSettingsTab: View { .foregroundStyle(.tertiary) } } + #endif divider @@ -192,9 +195,10 @@ struct macOSGeneralSettingsTab: View { store: store, themeManager: themeManager, userDefaults: userDefaults - ) { - appUpdater.resetToDefaults() - } + ) + #if canImport(Sparkle) + appUpdater.resetToDefaults() + #endif } } } diff --git a/BitDream/Views/macOS/macOSMenuCommands.swift b/BitDream/Views/macOS/macOSMenuCommands.swift index b8b810c..63d7506 100644 --- a/BitDream/Views/macOS/macOSMenuCommands.swift +++ b/BitDream/Views/macOS/macOSMenuCommands.swift @@ -393,7 +393,9 @@ struct AppearanceCommands: Commands { struct AppCommands: Commands { @Environment(\.openWindow) private var openWindow + #if canImport(Sparkle) @ObservedObject var appUpdater: AppUpdater + #endif var body: some Commands { CommandGroup(replacing: .appInfo) { @@ -403,12 +405,12 @@ struct AppCommands: Commands { Label("About BitDream", systemImage: "info.circle") }) - Button(action: { - appUpdater.checkForUpdates() - }, label: { + #if canImport(Sparkle) + Button(action: appUpdater.checkForUpdates) { Label("Check for Updates…", systemImage: "square.and.arrow.down") - }) + } .disabled(!appUpdater.canCheckForUpdates) + #endif } } } diff --git a/docs/build-settings.md b/docs/build-settings.md index 7003a9c..da564c1 100644 --- a/docs/build-settings.md +++ b/docs/build-settings.md @@ -5,6 +5,10 @@ - `Config/BuildSettings.example.local.xcconfig` (tracked): template for local setup. - `Config/BuildSettings.local.xcconfig` (ignored): required local/CI overrides. +These files configure the direct-download `BitDream` target. The +`BitDreamAppStore` target neither reads Sparkle settings nor requires a local +override file. + ## Local Setup 1. Copy `Config/BuildSettings.example.local.xcconfig` to `Config/BuildSettings.local.xcconfig`. 2. Set local values (currently `SPARKLE_APPCAST_URL` and `SPARKLE_PUBLIC_KEY`). @@ -12,3 +16,4 @@ ## CI / Release - CI writes `Config/BuildSettings.local.xcconfig` before archive builds. - For Sparkle, release workflow uses repo variables `SPARKLE_APPCAST_URL` and `SPARKLE_PUBLIC_KEY`. +- The Mac App Store build uses its dedicated Sparkle-free target and scheme. diff --git a/docs/mac-app-store.md b/docs/mac-app-store.md new file mode 100644 index 0000000..f0bbf8a --- /dev/null +++ b/docs/mac-app-store.md @@ -0,0 +1,50 @@ +# Mac App Store Distribution + +BitDream has separate distribution targets that share the same Swift sources +and assets: + +- `BitDream` and `BitDreamWidgetsExtension` produce the direct-download build. + The app target links Sparkle and the release workflow signs both targets with + Developer ID identities. +- `BitDreamAppStore` and `BitDreamWidgetsAppStoreExtension` produce the Mac App + Store build. Neither target links Sparkle, and both use automatic App Store + signing. + +The `BitDreamAppStore` scheme builds the complete App Store target graph. Code +and UI that depend on Sparkle are guarded with `canImport(Sparkle)`, so they are +compiled only when the direct-download target supplies the package product. +`BitDream/Info-AppStore.plist` contains no Sparkle configuration. + +## Validation + +Before submission, build the App Store scheme without creating the local +Sparkle settings file. Verify that the built app: + +- embeds the App Store widget extension; +- contains no Sparkle files; +- has no executable linked to Sparkle; and +- contains no Sparkle `SU...` keys in its Info.plist. + +Run the same compile check locally from the repository root: + +```bash +/Applications/Xcode.app/Contents/Developer/usr/bin/xcodebuild \ + -project BitDream.xcodeproj \ + -scheme BitDreamAppStore \ + -configuration Debug \ + -destination 'generic/platform=macOS' \ + CODE_SIGNING_ALLOWED=NO \ + CODE_SIGNING_REQUIRED=NO \ + build +``` + +## Archiving + +Select the `BitDreamAppStore` scheme in Xcode, choose **Any Mac** as the run +destination, and use **Product > Archive**. Confirm that Xcode selects the Mac +App Store provisioning profiles for both the app and widget extension before +uploading the archive through Organizer. + +Keep `MARKETING_VERSION` and `CURRENT_PROJECT_VERSION` synchronized between the +App Store app and widget targets. The direct-download release remains managed by +`.github/workflows/release.yml` and its Sparkle appcast workflow. diff --git a/docs/releases.md b/docs/releases.md index b2cb97c..fe5071c 100644 --- a/docs/releases.md +++ b/docs/releases.md @@ -12,3 +12,6 @@ Run the preflight checks, then create a stable release tag: Pushing the tag starts `.github/workflows/release.yml`, which builds, signs, notarizes, and publishes the macOS release. The workflow rejects tags that do not match the Xcode marketing version. See `docs/build-settings.md` and `docs/sparkle-updates.md` for configuration details. + +This workflow publishes the direct-download build. See +`docs/mac-app-store.md` for the separate Mac App Store archive process.