diff --git a/AltBackup/Info.plist b/AltBackup/Info.plist index 87318f17..f3345af1 100644 --- a/AltBackup/Info.plist +++ b/AltBackup/Info.plist @@ -37,6 +37,8 @@ BuildRevision $(BUILD_REVISION) + BuildChannel + $(BUILD_CHANNEL) CFBundleVersion $(CURRENT_PROJECT_VERSION) LSRequiresIPhoneOS diff --git a/AltStore.xcodeproj/project.pbxproj b/AltStore.xcodeproj/project.pbxproj index 914ff9a8..b2cab4ba 100644 --- a/AltStore.xcodeproj/project.pbxproj +++ b/AltStore.xcodeproj/project.pbxproj @@ -65,6 +65,8 @@ A86315DF2D3EB2DE0048FA40 /* ErrorProcessing.swift in Sources */ = {isa = PBXBuildFile; fileRef = A86315DE2D3EB2D80048FA40 /* ErrorProcessing.swift */; }; A868CFE42D31999A002F1201 /* SingletonGenericMap.swift in Sources */ = {isa = PBXBuildFile; fileRef = A868CFE32D319988002F1201 /* SingletonGenericMap.swift */; }; A8696EE42D34512C00E96389 /* RemoveAppExtensionsOperation.swift in Sources */ = {isa = PBXBuildFile; fileRef = A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */; }; + A888EAD52D401D8F0026F7E3 /* BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A888EAD42D401D8A0026F7E3 /* BuildInfo.swift */; }; + A888EAD62D4020770026F7E3 /* BuildInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = A888EAD42D401D8A0026F7E3 /* BuildInfo.swift */; }; A88B8C492D35AD3200F53F9D /* OperationsLoggingContolView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88B8C482D35AD3200F53F9D /* OperationsLoggingContolView.swift */; }; A88B8C552D35F1EC00F53F9D /* OperationsLoggingControl.swift in Sources */ = {isa = PBXBuildFile; fileRef = A88B8C542D35F1EC00F53F9D /* OperationsLoggingControl.swift */; }; A8945AA62D059B6100D86CBE /* Roxas.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A8945AA52D059B6100D86CBE /* Roxas.framework */; }; @@ -649,6 +651,7 @@ A86315DE2D3EB2D80048FA40 /* ErrorProcessing.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorProcessing.swift; sourceTree = ""; }; A868CFE32D319988002F1201 /* SingletonGenericMap.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SingletonGenericMap.swift; sourceTree = ""; }; A8696EE32D34512C00E96389 /* RemoveAppExtensionsOperation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RemoveAppExtensionsOperation.swift; sourceTree = ""; }; + A888EAD42D401D8A0026F7E3 /* BuildInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BuildInfo.swift; sourceTree = ""; }; A88B8C482D35AD3200F53F9D /* OperationsLoggingContolView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationsLoggingContolView.swift; sourceTree = ""; }; A88B8C542D35F1EC00F53F9D /* OperationsLoggingControl.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OperationsLoggingControl.swift; sourceTree = ""; }; A8945AA52D059B6100D86CBE /* Roxas.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Roxas.framework; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1224,6 +1227,14 @@ path = errors; sourceTree = ""; }; + A888EAD32D401D7C0026F7E3 /* buildinfo */ = { + isa = PBXGroup; + children = ( + A888EAD42D401D8A0026F7E3 /* BuildInfo.swift */, + ); + path = buildinfo; + sourceTree = ""; + }; A88B8C532D35F1E800F53F9D /* operations */ = { isa = PBXGroup; children = ( @@ -1288,6 +1299,7 @@ A8C38C1C2D2068D100E83DBD /* Utils */ = { isa = PBXGroup; children = ( + A888EAD32D401D7C0026F7E3 /* buildinfo */, A8AD35572D31BEB2003A28B4 /* datastructures */, A8A853AD2D3050CC00995795 /* pagination */, A8087E712D2D291B002DB21B /* importexport */, @@ -2910,6 +2922,7 @@ 0E05025C2BEC947000879B5C /* String+SideStore.swift in Sources */, 0EE7FDCB2BE8D12B00D1E390 /* ALTLocalizedError.swift in Sources */, BF66EEA92501AEC5007EE018 /* Tier.swift in Sources */, + A888EAD62D4020770026F7E3 /* BuildInfo.swift in Sources */, BF66EEDB2501AECA007EE018 /* StoreApp.swift in Sources */, D5CE309C2B4C946300DB8151 /* AltStore15ToAltStore16.xcmappingmodel in Sources */, BF66EEDE2501AECA007EE018 /* AppID.swift in Sources */, @@ -3054,6 +3067,7 @@ BFF00D302501BD7D00746320 /* Intents.intentdefinition in Sources */, D5B6F6AB2AD76541007EED5A /* PreviewAppScreenshotsViewController.swift in Sources */, BFD2476E2284B9A500981D42 /* AppDelegate.swift in Sources */, + A888EAD52D401D8F0026F7E3 /* BuildInfo.swift in Sources */, BF41B806233423AE00C593A3 /* TabBarController.swift in Sources */, BFE00A202503097F00EB4D0C /* INInteraction+AltStore.swift in Sources */, BFDB6A0B22AAEDB7007EA6D6 /* Operation.swift in Sources */, diff --git a/AltStore/App IDs/AppIDsViewController.swift b/AltStore/App IDs/AppIDsViewController.swift index 23e0ab90..83d45f94 100644 --- a/AltStore/App IDs/AppIDsViewController.swift +++ b/AltStore/App IDs/AppIDsViewController.swift @@ -195,7 +195,7 @@ extension AppIDsViewController: UICollectionViewDelegateFlowLayout // NOTE: double dequeue of cell has been discontinued // TODO: Using harcoded value until this is fixed - return CGSize(width: collectionView.bounds.width, height: 260) + return CGSize(width: collectionView.bounds.width, height: 200) } func collectionView(_ collectionView: UICollectionView, layout collectionViewLayout: UICollectionViewLayout, referenceSizeForFooterInSection section: Int) -> CGSize diff --git a/AltStore/Components/ToastView.swift b/AltStore/Components/ToastView.swift index f940b4ac..b563d811 100644 --- a/AltStore/Components/ToastView.swift +++ b/AltStore/Components/ToastView.swift @@ -65,13 +65,23 @@ class ToastView: RSTToastView self.opensErrorLog = opensLog } - convenience init(error: Error) + enum InfoMode: String { + case fullError + case localizedDescription + } + + convenience init(error: Error){ + self.init(error: error, mode: .localizedDescription) + } + + convenience init(error: Error, mode: InfoMode) { let error = error as NSError + let mode = mode == .fullError ? ErrorProcessing.InfoMode.fullError : ErrorProcessing.InfoMode.localizedDescription let text = error.localizedTitle ?? NSLocalizedString("Operation Failed", comment: "") - let detailText = ErrorProcessing(.fullError).getDescription(error: error) - + let detailText = ErrorProcessing(mode).getDescription(error: error) + self.init(text: text, detailText: detailText) } diff --git a/AltStore/Info.plist b/AltStore/Info.plist index cfe47fc9..031b11d9 100644 --- a/AltStore/Info.plist +++ b/AltStore/Info.plist @@ -83,6 +83,8 @@ $(CURRENT_PROJECT_VERSION) BuildRevision $(BUILD_REVISION) + BuildChannel + $(BUILD_CHANNEL) INIntentsSupported RefreshAllIntent diff --git a/AltStore/LaunchViewController.swift b/AltStore/LaunchViewController.swift index 13c42c80..d06242c6 100644 --- a/AltStore/LaunchViewController.swift +++ b/AltStore/LaunchViewController.swift @@ -316,7 +316,7 @@ extension LaunchViewController let errorDesc = ErrorProcessing(.fullError).getDescription(error: error as NSError) print("Failed to update sources on launch. \(errorDesc)") - let toastView = ToastView(error: error) + let toastView = ToastView(error: error, mode: .fullError) toastView.addTarget(self.destinationViewController, action: #selector(TabBarController.presentSources), for: .touchUpInside) toastView.show(in: self.destinationViewController.selectedViewController ?? self.destinationViewController) } diff --git a/AltStore/Managing Apps/AppManager.swift b/AltStore/Managing Apps/AppManager.swift index 39fc0c47..8081bc26 100644 --- a/AltStore/Managing Apps/AppManager.swift +++ b/AltStore/Managing Apps/AppManager.swift @@ -593,30 +593,30 @@ extension AppManager func updatePatronsIfNeeded() { - guard self.operationQueue.operations.allSatisfy({ !($0 is UpdatePatronsOperation) }) else { - // There's already an UpdatePatronsOperation running. - return - } - - self.updatePatronsResult = nil - - let updatePatronsOperation = UpdatePatronsOperation() - updatePatronsOperation.resultHandler = { (result) in - do - { - try result.get() - self.updatePatronsResult = .success(()) - } - catch - { - print("Error updating Friend Zone Patrons:", error) - self.updatePatronsResult = .failure(error) - } - - NotificationCenter.default.post(name: AppManager.didUpdatePatronsNotification, object: self) - } - - self.run([updatePatronsOperation], context: nil) +// guard self.operationQueue.operations.allSatisfy({ !($0 is UpdatePatronsOperation) }) else { +// // There's already an UpdatePatronsOperation running. +// return +// } +// +// self.updatePatronsResult = nil +// +// let updatePatronsOperation = UpdatePatronsOperation() +// updatePatronsOperation.resultHandler = { (result) in +// do +// { +// try result.get() +// self.updatePatronsResult = .success(()) +// } +// catch +// { +// print("Error updating Friend Zone Patrons:", error) +// self.updatePatronsResult = .failure(error) +// } +// +// NotificationCenter.default.post(name: AppManager.didUpdatePatronsNotification, object: self) +// } +// +// self.run([updatePatronsOperation], context: nil) } func updateAllSources(completion: @escaping (Result) -> Void) @@ -634,6 +634,9 @@ extension AppManager do { let (_, context) = try result.get() +// print("\n\n\n\(context.insertedObjects)\n\n\n") +// print("\n\n\n\(context.updatedObjects)\n\n\n") +// print("\n\n\n\(context.deletedObjects)\n\n\n") try context.save() DispatchQueue.main.async { @@ -1228,13 +1231,17 @@ private extension AppManager } else { - DispatchQueue.main.schedule { - UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.isIdleTimeoutDisableEnabled + // Disable the idleTimeout + if !UIApplication.shared.isIdleTimerDisabled { // accept only once if concurrent + DispatchQueue.main.schedule { + UIApplication.shared.isIdleTimerDisabled = UserDefaults.standard.isIdleTimeoutDisableEnabled + } } performAppOperations() - DispatchQueue.main.schedule { - UIApplication.shared.isIdleTimerDisabled = false - } + // Moved to self.finish() +// DispatchQueue.main.schedule { +// UIApplication.shared.isIdleTimerDisabled = false +// } } return group @@ -2095,6 +2102,16 @@ private extension AppManager func finish(_ operation: AppOperation, result: Result, group: RefreshGroup, progress: Progress?) { + // Remove disableIdleTimeout + // TODO: This should disable for the last finish() request not the first though for batches + // probably if we are in batch mode, we can count expected no of finishes() to arrive + // and schedule disabling only on last request by matching it with count. + if UIApplication.shared.isIdleTimerDisabled { // accept only once if concurrent + DispatchQueue.main.schedule { + UIApplication.shared.isIdleTimerDisabled = false + } + } + // Must remove before saving installedApp. if let currentProgress = self.progress(for: operation), currentProgress == progress { diff --git a/AltStore/Operations/Errors/OperationError.swift b/AltStore/Operations/Errors/OperationError.swift index 52486a1b..103287e0 100644 --- a/AltStore/Operations/Errors/OperationError.swift +++ b/AltStore/Operations/Errors/OperationError.swift @@ -238,10 +238,10 @@ struct OperationError: ALTLocalizedError { case .invalidParameters: let message = self._failureReason.map { ": \n\($0)" } ?? "." - return String(format: NSLocalizedString("Invalid parameters\n%@", comment: ""), message) + return String(format: NSLocalizedString("Invalid parameters%@", comment: ""), message) case .invalidOperationContext: let message = self._failureReason.map { ": \n\($0)" } ?? "." - return String(format: NSLocalizedString("Invalid Operation Context\n%@", comment: ""), message) + return String(format: NSLocalizedString("Invalid Operation Context%@", comment: ""), message) case .serverNotFound: return NSLocalizedString("AltServer could not be found.", comment: "") case .connectionFailed: return NSLocalizedString("A connection to AltServer could not be established.", comment: "") case .connectionDropped: return NSLocalizedString("The connection to AltServer was dropped.", comment: "") diff --git a/AltStore/Operations/Errors/VerificationError.swift b/AltStore/Operations/Errors/VerificationError.swift index 3219dfec..635a728d 100644 --- a/AltStore/Operations/Errors/VerificationError.swift +++ b/AltStore/Operations/Errors/VerificationError.swift @@ -155,11 +155,11 @@ struct VerificationError: ALTLocalizedError case .mismatchedVersion: let appName = self.$app.name ?? NSLocalizedString("the app", comment: "") - return String(format: NSLocalizedString("The downloaded version of %@ does not match the version specified by the source.", comment: ""), appName) + return String(format: NSLocalizedString("The downloaded version of %@ does not match the version specified by the source.\nExpected version: %@\nFound version: %@", comment: ""), appName, expectedVersion ?? "nil", version ?? "nil") case .mismatchedBuildVersion: let appName = self.$app.name ?? NSLocalizedString("the app", comment: "") - return String(format: NSLocalizedString("The downloaded version of %@ does not match the build number specified by the source.", comment: ""), appName) + return String(format: NSLocalizedString("The downloaded version of %@ does not match the build number specified by the source.\nExpected version: %@\nFound version: %@", comment: ""), appName, expectedVersion ?? "nil", version ?? "nil") case .undeclaredPermissions: let appName = self.$app.name ?? NSLocalizedString("The app", comment: "") diff --git a/AltStore/Operations/FetchProvisioningProfilesOperation.swift b/AltStore/Operations/FetchProvisioningProfilesOperation.swift index 471aabbb..0690b2f3 100644 --- a/AltStore/Operations/FetchProvisioningProfilesOperation.swift +++ b/AltStore/Operations/FetchProvisioningProfilesOperation.swift @@ -119,6 +119,36 @@ class FetchProvisioningProfilesOperation: ResultOperation<[String: ALTProvisioni return value } } + + internal func fetchProvisioningProfile(for appID: ALTAppID, app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) + { + ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in + switch Result(profile, error) + { + case .failure(let error): completionHandler(.failure(error)) + case .success(let profile): + + // Delete existing profile + ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in + switch Result(success, error) + { + case .failure: + // As of March 20, 2023, the free provisioning profile is re-generated each fetch, and you can no longer delete it. + // So instead, we just return the fetched profile from above. + completionHandler(.success(profile)) + + case .success: + Logger.sideload.notice("Generating new free provisioning profile for App ID \(appID.bundleIdentifier, privacy: .public).") + + // Fetch new provisioning profile + ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in + completionHandler(Result(profile, error)) + } + } + } + } + } + } } extension FetchProvisioningProfilesOperation @@ -223,7 +253,7 @@ extension FetchProvisioningProfilesOperation //process self.fetchProvisioningProfile( - for: appID, team: team, session: session, completionHandler: completionHandler + for: appID, app: app, team: team, session: session, completionHandler: completionHandler ) } } @@ -328,36 +358,6 @@ extension FetchProvisioningProfilesOperation } } } - - internal func fetchProvisioningProfile(for appID: ALTAppID, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) - { - ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in - switch Result(profile, error) - { - case .failure(let error): completionHandler(.failure(error)) - case .success(let profile): - - // Delete existing profile - ALTAppleAPI.shared.delete(profile, for: team, session: session) { (success, error) in - switch Result(success, error) - { - case .failure: - // As of March 20, 2023, the free provisioning profile is re-generated each fetch, and you can no longer delete it. - // So instead, we just return the fetched profile from above. - completionHandler(.success(profile)) - - case .success: - Logger.sideload.notice("Generating new free provisioning profile for App ID \(appID.bundleIdentifier, privacy: .public).") - - // Fetch new provisioning profile - ALTAppleAPI.shared.fetchProvisioningProfile(for: appID, deviceType: .iphone, team: team, session: session) { (profile, error) in - completionHandler(Result(profile, error)) - } - } - } - } - } - } } class FetchProvisioningProfilesRefreshOperation: FetchProvisioningProfilesOperation, @unchecked Sendable { @@ -374,8 +374,8 @@ class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperat } // modify Operations are allowed for the app groups and other stuffs - func fetchProvisioningProfile(appID: ALTAppID, - for app: ALTApplication, + override func fetchProvisioningProfile(for appID: ALTAppID, + app: ALTApplication, team: ALTTeam, session: ALTAppleAPISession, completionHandler: @escaping (Result) -> Void) @@ -396,7 +396,7 @@ class FetchProvisioningProfilesInstallOperation: FetchProvisioningProfilesOperat case .success(let appID): // Fetch Provisioning Profile - super.fetchProvisioningProfile(for: appID, team: team, session: session, completionHandler: completionHandler) + super.fetchProvisioningProfile(for: appID, app: app, team: team, session: session, completionHandler: completionHandler) } } } diff --git a/AltStore/Operations/RemoveAppExtensionsOperation.swift b/AltStore/Operations/RemoveAppExtensionsOperation.swift index da12500a..f60ce233 100644 --- a/AltStore/Operations/RemoveAppExtensionsOperation.swift +++ b/AltStore/Operations/RemoveAppExtensionsOperation.swift @@ -85,7 +85,7 @@ final class RemoveAppExtensionsOperation: ResultOperation presentingViewController.present(alertController, animated: true){ // if for any reason the view wasn't presented, then just signal that as error - if presentingViewController.presentedViewController == nil { + if presentingViewController.presentedViewController == nil && !alertController.isViewLoaded { let errMsg = "RemoveAppExtensionsOperation: unable to present dialog, view context not available." + "\nDid you move to different screen or background after starting the operation?" self.finish(.failure( diff --git a/AltStore/Settings/SettingsViewController.swift b/AltStore/Settings/SettingsViewController.swift index 88e09eb4..26d066c4 100644 --- a/AltStore/Settings/SettingsViewController.swift +++ b/AltStore/Settings/SettingsViewController.swift @@ -211,20 +211,11 @@ private extension SettingsViewController { private func getVersionLabel() -> String { - let MARKETING_VERSION_KEY = "CFBundleShortVersionString" - let BUILD_REVISION = "CFBundleRevision" // commit ID for now (but could be any, set by build env vars - let CURRENT_PROJECT_VERSION = kCFBundleVersionKey as String + let buildInfo = BuildInfo() func getXcodeVersion() -> String { - let XCODE_VERSION = "DTXcode" - let XCODE_REVISION = "DTXcodeBuild" - - let xcode = Bundle.main.object(forInfoDictionaryKey: XCODE_VERSION) as? String - let build = Bundle.main.object(forInfoDictionaryKey: XCODE_REVISION) as? String - - var xcodeVersion = xcode.map { version in -// " - Xcode \(version) - " + (build.map { revision in "\(revision)" } ?? "") // Ex: "0.6.0 - Xcode 16.2 - 21ac1ef" - "Xcode \(version) - " + (build.map { revision in "\(revision)" } ?? "") // Ex: "0.6.0 - Xcode 16.2 - 21ac1ef" + var xcodeVersion = buildInfo.xcode.map { version in + "Xcode \(version)" + (buildInfo.xcode_revision.map { revision in " - \(revision)" } ?? "") // Ex: "0.6.0 - Xcode 16.2 - 21ac1ef" } ?? "" if let pairing = Bundle.main.object(forInfoDictionaryKey: "ALTPairingFile") as? String, @@ -234,26 +225,25 @@ private extension SettingsViewController return xcodeVersion } + var versionLabel: String = "" if let installedApp = InstalledApp.fetchAltStore(in: DatabaseManager.shared.viewContext) { - #if BETA - // Only show build version (and build revision) for BETA builds. - let bundleVersion: String? = Bundle.main.object(forInfoDictionaryKey: CURRENT_PROJECT_VERSION) as? String - let buildRevision: String? = Bundle.main.object(forInfoDictionaryKey: BUILD_REVISION) as? String + let isStableBuild = (buildInfo.channel == .stable) + let revision = buildInfo.revision ?? "" - var localizedVersion = bundleVersion.map { version in - "\(installedApp.version) (\(version))" + (buildRevision.map { revision in " - \(revision)" } ?? "") // Ex: "0.6.0 (0600) - 1acdef3" - } ?? installedApp.localizedVersion - - #else var localizedVersion = installedApp.version - #endif - + // Only show build version (and build revision) for non stable builds. + if !isStableBuild { + localizedVersion += buildInfo.project_version.map{ version in + version.isEmpty ? "" : " (\(version))" + (revision.isEmpty ? "" : " - \(revision)") + } ?? installedApp.localizedVersion + } + versionLabel = NSLocalizedString(String(format: "Version %@", localizedVersion), comment: "SideStore Version") } - else if let version = Bundle.main.object(forInfoDictionaryKey: MARKETING_VERSION_KEY) as? String + else if let version = buildInfo.marketing_version { var version = "SideStore \(version)" @@ -268,10 +258,11 @@ private extension SettingsViewController versionLabel = NSLocalizedString(version, comment: "SideStore Version") } - // add xcode build version if in debug mode - #if DEBUG - versionLabel += "\n\(getXcodeVersion())" - #endif + // add xcode build version for local builds + if buildInfo.channel == .local + { + versionLabel += "\n\(getXcodeVersion())" + } return versionLabel } diff --git a/AltStore/Sources/Sources.storyboard b/AltStore/Sources/Sources.storyboard index 5782dda2..35cc8703 100644 --- a/AltStore/Sources/Sources.storyboard +++ b/AltStore/Sources/Sources.storyboard @@ -13,11 +13,11 @@ - + - + @@ -33,7 +33,7 @@ - + @@ -59,9 +59,9 @@ - + - + @@ -75,7 +75,7 @@ - + @@ -87,7 +87,7 @@ - + @@ -95,7 +95,7 @@ - + @@ -122,7 +122,7 @@ - + @@ -148,7 +148,7 @@ - + @@ -160,7 +160,7 @@ - + @@ -192,7 +192,7 @@ - + @@ -226,7 +226,6 @@ - @@ -238,10 +237,10 @@ - + - + diff --git a/AltStoreCore/Info.plist b/AltStoreCore/Info.plist index cebb1257..cc1fbfd2 100644 --- a/AltStoreCore/Info.plist +++ b/AltStoreCore/Info.plist @@ -18,6 +18,8 @@ $(MARKETING_VERSION) BuildRevision $(BUILD_REVISION) + BuildChannel + $(BUILD_CHANNEL) CFBundleVersion $(CURRENT_PROJECT_VERSION) diff --git a/AltWidget/Info.plist b/AltWidget/Info.plist index cf410522..31fda5f7 100644 --- a/AltWidget/Info.plist +++ b/AltWidget/Info.plist @@ -24,6 +24,8 @@ $(MARKETING_VERSION) BuildRevision $(BUILD_REVISION) + BuildChannel + $(BUILD_CHANNEL) CFBundleVersion $(CURRENT_PROJECT_VERSION) NSExtension diff --git a/Build.xcconfig b/Build.xcconfig index 93135631..d46cb8d0 100644 --- a/Build.xcconfig +++ b/Build.xcconfig @@ -3,6 +3,7 @@ MARKETING_VERSION = 0.6.0 CURRENT_PROJECT_VERSION = 6000 +BUILD_CHANNEL = stable // Vars to be overwritten by `CodeSigning.xcconfig` if exists DEVELOPMENT_TEAM = S32Z3HMYVQ diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 78443779..8f4b9c6e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -50,17 +50,55 @@ to force it to check for new binaries, run `bash ./SideStore/fetch-prebuilt.sh f ## Building an IPA for distribution Install cocoapods if required using: `brew install cocoapods` - Now perform Pod-Install using: `pod install` command to install the dependencies. -You can then use the Makefile command: `make build fakesign ipa` in the root directory. - +You can then use the Makefile command: `make build fakesign ipa` in the root directory. +By default the config for build is: `Release` +For debug builds: `export BUILD_CONFIG=Debug;make build fakesign ipa` in the root directory. +For alpha/beta builds: `export IS_ALPHA=1;` or `export IS_BETA=1;` before invoking the build command. This will create SideStore.ipa. +```sh +Examples: + + # cocoapods + brew install cocoapods + # perform installation of the pods + pod install + + # alpha release build + export IS_ALPHA=1;make build fakesign ipa + # alpha debug build + export IS_ALPHA=1;export BUILD_CONFIG=Debug;make build fakesign ipa + + # beta release build + export IS_BETA=1;make build fakesign ipa + # beta debug build + export IS_BETA=1;export BUILD_CONFIG=Debug;make build fakesign ipa + + # stable release build + make build fakesign ipa + # stable debug build + export BUILD_CONFIG=Debug;make build fakesign ipa +``` +By default sidestore will build for its default bundleIdentifier `com.SideStore.SideStore` but if you need to set a custom bundleID for commandline builds, once can do so by exporting `BUNDLE_ID_SUFFIX` env var +```sh + # stable release build + export BUNDLE_ID_SUFFIX=XYZ0123456;make build fakesign ipa + # stable debug build + export BUNDLE_ID_SUFFIX=XYZ0123456;export BUILD_CONFIG=Debug;make build fakesign ipa +``` +NOTE: When building from XCode, the `BUNDLE_ID_SUFFIX` is set by default with the value of `DEVELOPMENT_TEAM` + +This can be customized by setting/removing the BUNDLE_ID_SUFFIX in overriding CodeSigning.xcconfig created from CodeSigning.xcconfig.sample + + + > **Warning** > > The binary created will contain paths to Xcode's DerivedData, and if you built minimuxer on your machine, paths to $HOME/.cargo. This will include your username. If you want to keep your user's > username private, you might want to get GitHub Actions to build the IPA instead. +> ## Developing minimuxer alongside SideStore diff --git a/CodeSigning.xcconfig.sample b/CodeSigning.xcconfig.sample index e0a5f1b0..31a8c67a 100644 --- a/CodeSigning.xcconfig.sample +++ b/CodeSigning.xcconfig.sample @@ -15,6 +15,10 @@ ORG_IDENTIFIER = com.myuniquename // we don't want to do this for release since those builds will most likely be installed via SideServer, which adds the team ID BUNDLE_ID_SUFFIX = .$(DEVELOPMENT_TEAM) +// Comment this out to use default 'stable' channel defined in Build.xcconfig +// For local xcode or commandline builds we can use local channel for local build specific logic +BUILD_CHANNEL = local + // Set to YES if you have a valid paid Apple Developer account DEVELOPER_ACCOUNT_PAID = NO diff --git a/Dependencies/Roxas b/Dependencies/Roxas index 03f60139..0784711e 160000 --- a/Dependencies/Roxas +++ b/Dependencies/Roxas @@ -1 +1 @@ -Subproject commit 03f6013972ff650a7c04c3d6397751c75920b3d8 +Subproject commit 0784711ed9a3a0bdb5cc57bde35d2c621691cf74 diff --git a/Makefile b/Makefile index b6e14108..c2a33d47 100755 --- a/Makefile +++ b/Makefile @@ -161,19 +161,24 @@ test: IS_ALPHA_TRUE := $(filter true TRUE 1, $(IS_ALPHA)) IS_BETA_TRUE := $(filter true TRUE 1, $(IS_BETA)) +# set build types to embed into Info.plist for key: BuildChannel +BUILD_CHANNEL := stable +BUILD_CHANNEL := $(if $(IS_ALPHA_TRUE),alpha,$(BUILD_CHANNEL)) +BUILD_CHANNEL := $(if $(IS_BETA_TRUE),beta,$(BUILD_CHANNEL)) + # Fetch the latest commit ID for ALPHA or BETA builds COMMIT_ID := $(if $(or $(IS_ALPHA_TRUE),$(IS_BETA_TRUE)),$(shell git rev-parse --short HEAD),) # Print release type based on the value of IS_ALPHA or IS_BETA print_release_type: @echo "" - @if [ "$(filter true TRUE 1,$(IS_ALPHA))" ]; then \ + @if [ $(IS_ALPHA_TRUE) ]; then \ echo "'IS_ALPHA' is set to true. Fetched the latest commit ID from HEAD..."; \ echo " Commit ID: $(COMMIT_ID)"; \ echo ""; \ echo ">>>>>>>> This is now an ALPHA release for COMMIT_ID = '$(COMMIT_ID)' <<<<<<<<<"; \ echo " Building with BUILD_REVISION = '$(COMMIT_ID)'"; \ - elif [ "$(filter true TRUE 1,$(IS_BETA))" ]; then \ + elif [ $(IS_BETA_TRUE) ]; then \ echo "'IS_BETA' is set to true. Fetched the latest commit ID from HEAD..."; \ echo " Commit ID: $(COMMIT_ID)"; \ echo ""; \ @@ -215,6 +220,7 @@ build: print_release_type DEVELOPMENT_TEAM=XYZ0123456 \ ORG_IDENTIFIER=com.SideStore \ BUILD_REVISION=$(COMMIT_ID) \ + BUILD_CHANNEL=$(BUILD_CHANNEL) BUNDLE_ID_SUFFIX= # DWARF_DSYM_FOLDER_PATH="." diff --git a/SideStore/Utils/buildinfo/BuildInfo.swift b/SideStore/Utils/buildinfo/BuildInfo.swift new file mode 100644 index 00000000..5e800ba1 --- /dev/null +++ b/SideStore/Utils/buildinfo/BuildInfo.swift @@ -0,0 +1,58 @@ +// +// BuildInfo.swift +// AltStore +// +// Created by Magesh K on 21/01/25. +// Copyright © 2025 SideStore. All rights reserved. +// + +public class BuildInfo{ + private static let BUILD_REVISION_TAG = "BuildRevision" // commit ID for now (but could be any, set by build env vars + private static let BUILD_CHANNEL_TAG = "BuildChannel" // set by build env, ex CI will set it via env vars, for xcode builds this is empty + + private static let MARKETING_VERSION_TAG = "CFBundleShortVersionString" + private static let CURRENT_PROJECT_VERSION_TAG = kCFBundleVersionKey as String + + private static let XCODE_VERSION_TAG = "DTXcode" + private static let XCODE_REVISION_TAG = "DTXcodeBuild" + + public enum Channel: String { + case unknown + case local // xcodebuilds can use this by setting BUILD_CHANNEL in CodeSigning.xcconfig + + case alpha + case beta + case stable + } + + public lazy var channel: Channel = { + let channel = Bundle.main.object(forInfoDictionaryKey: Self.BUILD_CHANNEL_TAG) as? String + return Channel(rawValue: channel ?? "") ?? .unknown + }() + + public lazy var revision: String? = { + let revision = Bundle.main.object(forInfoDictionaryKey: Self.BUILD_REVISION_TAG) as? String + return revision + }() + + public lazy var project_version: String? = { + let revision = Bundle.main.object(forInfoDictionaryKey: Self.CURRENT_PROJECT_VERSION_TAG) as? String + return revision + }() + + public lazy var marketing_version: String? = { + let revision = Bundle.main.object(forInfoDictionaryKey: Self.MARKETING_VERSION_TAG) as? String + return revision + }() + + public lazy var xcode: String? = { + let xcode = Bundle.main.object(forInfoDictionaryKey: Self.XCODE_VERSION_TAG) as? String + return xcode + }() + + public lazy var xcode_revision: String? = { + let revision = Bundle.main.object(forInfoDictionaryKey: Self.XCODE_REVISION_TAG) as? String + return revision + }() + +} diff --git a/SideStore/Utils/dignostics/errors/ErrorProcessing.swift b/SideStore/Utils/dignostics/errors/ErrorProcessing.swift index 15489f58..3e46c659 100644 --- a/SideStore/Utils/dignostics/errors/ErrorProcessing.swift +++ b/SideStore/Utils/dignostics/errors/ErrorProcessing.swift @@ -27,7 +27,7 @@ class ErrorProcessing { self.recur = recur } - private func processError(_ error: NSError, getMoreErrors: (_ error: NSError)->String) -> String{ + private func processError(_ error: NSError, ignoreTitle: Bool = false, getMoreErrors: (_ error: NSError)->String) -> String{ // if unique was requested and if this error is duplicate, ignore processing it let serializedError = "\(error)" if unique && errors.contains(serializedError) { @@ -39,7 +39,7 @@ class ErrorProcessing { var desc = "" switch (info){ case .localizedDescription: - title = (error.localizedTitle.map{$0+"\n"} ?? "") + title = !ignoreTitle ? (error.localizedTitle.map{$0+"\n"} ?? "") : "" desc = error.localizedDescription case .fullError: desc = serializedError @@ -54,13 +54,14 @@ class ErrorProcessing { return getDescriptionText(error: error) } - private lazy var recurseErrors = { error in - self.getDescriptionText(error: error) // recursively process underlying error(s) if any - } - func getDescriptionText(error: NSError) -> String{ + func getDescriptionText(error: NSError,_ depth: Int = 0) -> String{ + // closure + let recurseErrors = { error in + self.getDescriptionText(error: error, depth+1) // recursively process underlying error(s) if any + } + var description = "" - // process current error only if recur was not requested let processMoreErrors = recur ? recurseErrors : {_ in ""} @@ -74,7 +75,9 @@ class ErrorProcessing { let error = underlyingError as NSError description += processError(error, getMoreErrors: processMoreErrors) } else { - description += processError(error, getMoreErrors: processMoreErrors) + // ignore the title for the base error since we wanted this to be description + let isBaseError = (depth == 0) + description += processError(error, ignoreTitle: isBaseError, getMoreErrors: processMoreErrors) } return description }