diff --git a/.build-sample-apps.tmuxinator.yml b/.build-sample-apps.tmuxinator.yml new file mode 100644 index 0000000000..ceaa03cc4b --- /dev/null +++ b/.build-sample-apps.tmuxinator.yml @@ -0,0 +1,25 @@ + name: build-sample-apps + + windows: + - build: + layout: tiled + panes: + - bash -c 'cd ./Samples/iOS13-Swift && xcodebuild build -scheme iOS13-Swift | xcbeautify; echo $? > /tmp/iOS13-Swift.exit' + - bash -c 'cd ./Samples/iOS-SwiftUI && xcodebuild build -scheme iOS-SwiftUI | xcbeautify; echo $? > /tmp/iOS-SwiftUI.exit' + - bash -c 'cd ./Samples/iOS-Swift6 && xcodebuild build -scheme iOS-Swift6 | xcbeautify; echo $? > /tmp/iOS-Swift6.exit' + - bash -c 'cd ./Samples/iOS-Swift && xcodebuild build -scheme iOS-Swift | xcbeautify; echo $? > /tmp/iOS-Swift.exit' + - bash -c 'cd ./Samples/watchOS-Swift && xcodebuild build -scheme "watchOS-Swift WatchKit App" | xcbeautify; echo $? > /tmp/watchOS-Swift.exit' + - bash -c 'cd ./Samples/visionOS-Swift && xcodebuild build -scheme visionOS-Swift | xcbeautify; echo $? > /tmp/visionOS-Swift.exit' + - bash -c 'cd ./Samples/tvOS-Swift && xcodebuild build -scheme tvOS-Swift | xcbeautify; echo $? > /tmp/tvOS-Swift.exit' + - bash -c 'cd ./Samples/macOS-SwiftUI && xcodebuild build -scheme macOS-SwiftUI | xcbeautify; echo $? > /tmp/macOS-SwiftUI.exit' + - bash -c 'cd ./Samples/macOS-Swift && xcodebuild build -scheme macOS-Swift | xcbeautify; echo $? > /tmp/macOS-Swift.exit' + - bash -c 'cd ./Samples/iOS15-SwiftUI && xcodebuild build -scheme iOS15-SwiftUI | xcbeautify; echo $? > /tmp/iOS15-SwiftUI.exit' + - bash -c 'cd ./Samples/iOS-ObjectiveC && xcodebuild build -scheme iOS-ObjectiveC | xcbeautify; echo $? > /tmp/iOS-ObjectiveC.exit' + - | + while [ ! -f /tmp/iOS13-Swift.exit ] || [ ! -f /tmp/iOS-SwiftUI.exit ] || [ ! -f /tmp/iOS-Swift6.exit ] || [ ! -f /tmp/iOS-Swift.exit ] || [ ! -f /tmp/watchOS-Swift.exit ] || [ ! -f /tmp/visionOS-Swift.exit ] || [ ! -f /tmp/tvOS-Swift.exit ] || [ ! -f /tmp/macOS-SwiftUI.exit ] || [ ! -f /tmp/macOS-Swift.exit ] || [ ! -f /tmp/iOS15-SwiftUI.exit ] || [ ! -f /tmp/iOS-ObjectiveC.exit ]; do + sleep 1 + done + for file in /tmp/*.exit; do + echo "$file: $(cat $file)" + done + rm /tmp/*.exit diff --git a/Brewfile b/Brewfile index 1a0e3ad2b9..0868b4a91c 100644 --- a/Brewfile +++ b/Brewfile @@ -4,4 +4,6 @@ brew 'pre-commit' brew 'python3' brew 'xcbeautify' brew 'rbenv' +brew 'tmux' +brew 'tmuxinator' brew 'xcodegen' diff --git a/Makefile b/Makefile index f00e6c6a5e..454554564f 100644 --- a/Makefile +++ b/Makefile @@ -159,3 +159,8 @@ xcode: xcodegen --spec Samples/visionOS-Swift/visionOS-Swift.yml xcodegen --spec Samples/watchOS-Swift/watchOS-Swift.yml open Sentry.xcworkspace + +.PHONY: build-sample-apps + +build-sample-apps: + tmuxinator start -p .build-sample-apps.tmuxinator.yml diff --git a/Samples/SentrySampleShared/SentrySampleShared/EnvironmentVariableTableViewCell.swift b/Samples/SentrySampleShared/SentrySampleShared/EnvironmentVariableTableViewCell.swift index 120443ad4b..b8528236dd 100644 --- a/Samples/SentrySampleShared/SentrySampleShared/EnvironmentVariableTableViewCell.swift +++ b/Samples/SentrySampleShared/SentrySampleShared/EnvironmentVariableTableViewCell.swift @@ -50,6 +50,7 @@ class EnvironmentVariableTableViewCell: UITableViewCell, UITextFieldDelegate { } else { override?.stringValue = textField.text } + SentrySDKWrapper.shared.startSentry() } } #endif // !os(macOS) && !os(tvOS) && !os(watchOS) diff --git a/Samples/SentrySampleShared/SentrySampleShared/LaunchArgumentTableViewCell.swift b/Samples/SentrySampleShared/SentrySampleShared/LaunchArgumentTableViewCell.swift index 8264fc2a23..b70d480c60 100644 --- a/Samples/SentrySampleShared/SentrySampleShared/LaunchArgumentTableViewCell.swift +++ b/Samples/SentrySampleShared/SentrySampleShared/LaunchArgumentTableViewCell.swift @@ -1,4 +1,5 @@ #if !os(macOS) && !os(tvOS) && !os(watchOS) +import Sentry import UIKit class LaunchArgumentTableViewCell: UITableViewCell { @@ -12,6 +13,7 @@ class LaunchArgumentTableViewCell: UITableViewCell { @objc func toggleFlag() { override?.boolValue = flagSwitch.isOn + SentrySDKWrapper.shared.startSentry() } override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { diff --git a/Samples/SentrySampleShared/SentrySampleShared/SentrySDKOverrides.swift b/Samples/SentrySampleShared/SentrySampleShared/SentrySDKOverrides.swift index b4d33ffbe2..ef634d1a36 100644 --- a/Samples/SentrySampleShared/SentrySampleShared/SentrySDKOverrides.swift +++ b/Samples/SentrySampleShared/SentrySampleShared/SentrySDKOverrides.swift @@ -33,6 +33,7 @@ public enum SentrySDKOverrides { public enum Special: String, SentrySDKOverride { case wipeDataOnLaunch = "--io.sentry.wipe-data" case disableEverything = "--io.sentry.disable-everything" + case skipSDKInit = "--skip-sentry-init" public var boolValue: Bool { get { @@ -94,10 +95,63 @@ public enum SentrySDKOverrides { } } + public enum SessionReplay: String, SentrySDKOverride { + case disableSessionReplay = "--disable-session-replay" + case disableViewRendererV2 = "--io.sentry.session-replay.disableViewRendereV2" + case enableFastViewRendering = "--io.sentry.session-replay.enableFastViewRendering" + case sessionReplaySampleRate = "--io.sentry.sessionReplaySampleRate" + case sessionReplayOnErrorSampleRate = "--io.sentry.sessionReplayOnErrorSampleRate" + case sessionReplayQuality = "--io.sentry.sessionReplayQuality" + + public var booleanValue: Bool { + get { + switch self { + case .sessionReplaySampleRate, .sessionReplayOnErrorSampleRate, .sessionReplayQuality: fatalError("This override doesn't correspond to a boolean value.") + default: return getBoolOverride(for: rawValue) + } + } + set(newValue) { + switch self { + case .sessionReplaySampleRate, .sessionReplayOnErrorSampleRate, .sessionReplayQuality: fatalError("This override doesn't correspond to a boolean value.") + default: setBoolOverride(for: rawValue, value: newValue) + } + } + } + + public var floatValue: Float? { + get { + switch self { + case .sessionReplaySampleRate, .sessionReplayOnErrorSampleRate: return getFloatValueOverride(for: rawValue) + default: fatalError("This override doesn't correspond to a float value.") + } + } + set(newValue) { + switch self { + case .sessionReplaySampleRate, .sessionReplayOnErrorSampleRate: setFloatOverride(for: rawValue, value: newValue) + default: fatalError("This override doesn't correspond to a float value.") + } + } + } + + public var stringValue: String? { + get { + switch self { + case .sessionReplayQuality: return getStringValueOverride(for: rawValue) + default: fatalError("This override doesn't correspond to a string value.") + } + } + set(newValue) { + switch self { + case .sessionReplayQuality: setStringOverride(for: rawValue, value: newValue) + default: fatalError("This override doesn't correspond to a string value.") + } + } + } + } + public enum Other: String, SentrySDKOverride { case disableAttachScreenshot = "--disable-attach-screenshot" case disableAttachViewHierarchy = "--disable-attach-view-hierarchy" - case disableSessionReplay = "--disable-session-replay" case disableMetricKit = "--disable-metrickit-integration" case disableBreadcrumbs = "--disable-automatic-breadcrumbs" case disableNetworkBreadcrumbs = "--disable-network-breadcrumbs" @@ -135,7 +189,7 @@ public enum SentrySDKOverrides { } } - public static var boolValues: [Other] { [.disableAttachScreenshot, .disableAttachViewHierarchy, .disableSessionReplay, .disableMetricKit, .disableBreadcrumbs, .disableNetworkBreadcrumbs, .disableSwizzling, .disableCrashHandling, .disableSpotlight, .disableFileManagerSwizzling] } + public static var boolValues: [Other] { [.disableAttachScreenshot, .disableAttachViewHierarchy, .disableMetricKit, .disableBreadcrumbs, .disableNetworkBreadcrumbs, .disableSwizzling, .disableCrashHandling, .disableSpotlight, .disableFileManagerSwizzling] } public static var stringVars: [Other] { [.userName, .userEmail] } } diff --git a/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift b/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift index daf7b34d68..07d6722d5f 100644 --- a/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift +++ b/Samples/SentrySampleShared/SentrySampleShared/SentrySDKWrapper.swift @@ -21,7 +21,14 @@ public struct SentrySDKWrapper { #endif // !os(macOS) && !os(tvOS) && !os(watchOS) public func startSentry() { - SentrySDK.start(configureOptions: configureSentryOptions(options:)) + if SentrySDK.isEnabled { + print("SentrySDK already enabled, closing it") + SentrySDK.close() + } + + if !SentrySDKOverrides.Special.skipSDKInit.boolValue { + SentrySDK.start(configureOptions: configureSentryOptions(options:)) + } } func configureSentryOptions(options: Options) { @@ -33,17 +40,21 @@ public struct SentrySDKWrapper { options.debug = true #if !os(macOS) && !os(watchOS) && !os(visionOS) - if #available(iOS 16.0, *), !SentrySDKOverrides.Other.disableSessionReplay.boolValue { + if #available(iOS 16.0, *), !SentrySDKOverrides.SessionReplay.disableSessionReplay.boolValue { options.sessionReplay = SentryReplayOptions( - sessionSampleRate: 0, - onErrorSampleRate: 1, + sessionSampleRate: SentrySDKOverrides.SessionReplay.sessionReplaySampleRate.floatValue ?? 0, + onErrorSampleRate: SentrySDKOverrides.SessionReplay.sessionReplayOnErrorSampleRate.floatValue ?? 1, maskAllText: true, maskAllImages: true ) - options.sessionReplay.quality = .high - options.sessionReplay.enableViewRendererV2 = true - // Disable the fast view renderering, because we noticed parts (like the tab bar) are not rendered correctly - options.sessionReplay.enableFastViewRendering = false + + let defaultReplayQuality = SentryReplayOptions.SentryReplayQuality.high + options.sessionReplay.quality = SentryReplayOptions.SentryReplayQuality(rawValue: (SentrySDKOverrides.SessionReplay.sessionReplayQuality.stringValue as? NSString)?.integerValue ?? defaultReplayQuality.rawValue) ?? defaultReplayQuality + + options.sessionReplay.enableViewRendererV2 = !SentrySDKOverrides.SessionReplay.disableViewRendererV2.boolValue + + // Disable the fast view rendering, because we noticed parts (like the tab bar) are not rendered correctly + options.sessionReplay.enableFastViewRendering = SentrySDKOverrides.SessionReplay.enableFastViewRendering.boolValue } #if !os(tvOS) @@ -378,13 +389,11 @@ extension SentrySDKWrapper { var args: [String] { let args = ProcessInfo.processInfo.arguments - print("[iOS-Swift] [debug] launch arguments: \(args)") return args } var env: [String: String] { let env = ProcessInfo.processInfo.environment - print("[iOS-Swift] [debug] environment: \(env)") return env } diff --git a/Samples/SessionReplay-CameraTest/SessionReplay-CameraTest.yml b/Samples/SessionReplay-CameraTest/SessionReplay-CameraTest.yml index 29e20781e9..44bb23891d 100644 --- a/Samples/SessionReplay-CameraTest/SessionReplay-CameraTest.yml +++ b/Samples/SessionReplay-CameraTest/SessionReplay-CameraTest.yml @@ -1,4 +1,6 @@ name: SessionReplay-CameraTest +include: + - ../Shared/feature-flags.yml createIntermediateGroups: true generateEmptyDirectories: true projectReferences: @@ -32,3 +34,10 @@ targets: - script: ../Shared/reset-git-info.sh name: Reset Git Fields in Info.plist basedOnDependencyAnalysis: false +schemes: + SessionReplay-CameraTest: + templates: + - SampleAppScheme + build: + targets: + SessionReplay-CameraTest: all diff --git a/Samples/SessionReplay-CameraTest/Sources/AppDelegate.swift b/Samples/SessionReplay-CameraTest/Sources/AppDelegate.swift index 62a7050130..588925ae31 100644 --- a/Samples/SessionReplay-CameraTest/Sources/AppDelegate.swift +++ b/Samples/SessionReplay-CameraTest/Sources/AppDelegate.swift @@ -4,29 +4,9 @@ import UIKit @main class AppDelegate: UIResponder, UIApplicationDelegate { - - static var isSentryEnabled = true - static var isSessionReplayEnabled = true - static var isViewRendererV2Enabled = true - func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { - AppDelegate.reloadSentrySDK() - - return true - } - - static func reloadSentrySDK() { - if SentrySDK.isEnabled { - print("SentrySDK already started, closing it") - SentrySDK.close() - } - - if !isSentryEnabled { - print("SentrySDK disabled") - return - } - SentrySDKWrapper.shared.startSentry() + return true } // MARK: UISceneSession Lifecycle diff --git a/Samples/SessionReplay-CameraTest/Sources/Base.lproj/Main.storyboard b/Samples/SessionReplay-CameraTest/Sources/Base.lproj/Main.storyboard index 055522d904..3774cc501b 100644 --- a/Samples/SessionReplay-CameraTest/Sources/Base.lproj/Main.storyboard +++ b/Samples/SessionReplay-CameraTest/Sources/Base.lproj/Main.storyboard @@ -1,8 +1,9 @@ - + - + + @@ -17,96 +18,31 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + - - + - - - - - @@ -115,9 +51,6 @@ - - - diff --git a/Samples/SessionReplay-CameraTest/Sources/ViewController.swift b/Samples/SessionReplay-CameraTest/Sources/ViewController.swift index b6c9cca70d..592456f753 100644 --- a/Samples/SessionReplay-CameraTest/Sources/ViewController.swift +++ b/Samples/SessionReplay-CameraTest/Sources/ViewController.swift @@ -1,5 +1,6 @@ import AVFoundation import Sentry +import SentrySampleShared import UIKit class ViewController: UIViewController { @@ -88,17 +89,7 @@ class ViewController: UIViewController { } } - @IBAction func didChangeToggleValue(_ sender: UISwitch) { - if sender === enableSessionReplaySwitch { - AppDelegate.isSessionReplayEnabled = sender.isOn - print("Enable session replay flag changed to: \(AppDelegate.isSessionReplayEnabled)") - } else if sender === useViewRendererV2Switch { - AppDelegate.isViewRendererV2Enabled = sender.isOn - print("Use view renderer V2 flag changed to: \(AppDelegate.isViewRendererV2Enabled)") - } else if sender === enableSentrySwitch { - AppDelegate.isSentryEnabled = sender.isOn - print("Enable Sentry flag changed to: \(AppDelegate.isSentryEnabled)") - } - AppDelegate.reloadSentrySDK() + @IBAction func configureSDK(_ sender: Any) { + present(FeaturesViewController(style: .plain), animated: true) } } diff --git a/Samples/Shared/feature-flags.yml b/Samples/Shared/feature-flags.yml index 3025987c72..bce2899177 100644 --- a/Samples/Shared/feature-flags.yml +++ b/Samples/Shared/feature-flags.yml @@ -2,19 +2,18 @@ schemeTemplates: SampleAppScheme: run: commandLineArguments: + "--io.sentry.schema-environment-variable-precedence": true "--io.sentry.disable-everything": false + "--skip-sentry-init": false + "--io.sentry.wipe-data": false + + # session replay + "--disable-session-replay": false + "--io.sentry.session-replay.disableViewRendereV2": false + "--io.sentry.session-replay.enableFastViewRendering": false + + # user feedback "--io.sentry.ui-test.use-custom-feedback-button": true - "--io.sentry.disable-ui-profiling": false - "--disable-app-hang-tracking-v2": false - "--io.sentry.schema-environment-variable-precedence": true - "--io.sentry.profile-lifecycle-manual": false - "--disable-time-to-full-display-tracing": false - "--disable-performance-v2": false - "--disable-attach-view-hierarchy": false - "--disable-attach-screenshot": false - "--io.sentry.base64-attachment-data": false - "--io.sentry.disable-http-transport": false - "--disable-file-io-tracing": false "--io.sentry.feedback.dont-use-sentry-user": false "--io.sentry.feedback.require-name": false "--io.sentry.feedback.require-email": false @@ -22,16 +21,25 @@ schemeTemplates: "--io.sentry.feedback.no-widget-icon": false "--io.sentry.feedback.no-widget-text": false "--io.sentry.feedback.all-defaults": false - "--skip-sentry-init": false - "--disable-spotlight": false + "--io.sentry.feedback.no-auto-inject-widget": false + + # profiling + "--io.sentry.disable-ui-profiling": false + "--io.sentry.profile-lifecycle-manual": false + "--io.sentry.slow-load-method": false + "--io.sentry.disable-app-start-profiling": false + + # performance + "--disable-app-hang-tracking-v2": false + "--disable-time-to-full-display-tracing": false + "--disable-performance-v2": false + "--disable-attach-view-hierarchy": false + "--disable-attach-screenshot": false + "--disable-file-io-tracing": false "--disable-automatic-session-tracking": false "--disable-metrickit-integration": false - "--disable-session-replay": false - "--io.sentry.wipe-data": false - "--io.sentry.slow-load-method": false "--disable-watchdog-tracking": false "--disable-tracing": false - "--io.sentry.disable-app-start-profiling": false "--disable-crash-handler": false "--disable-swizzling": false "--disable-network-breadcrumbs": false @@ -42,9 +50,25 @@ schemeTemplates: "--disable-anr-tracking": false "--disable-auto-performance-tracing": false "--disable-ui-tracing": false - "--io.sentry.feedback.no-auto-inject-widget": false "--disable-filemanager-swizzling": false + + # other + "--io.sentry.base64-attachment-data": false + "--io.sentry.disable-http-transport": false + "--disable-spotlight": false + environmentVariables: + # session replay + - variable: "--io.sentry.sessionReplaySampleRate" + value: + isEnabled: false + - variable: "--io.sentry.sessionReplayOnErrorSampleRate" + value: + isEnabled: false + - variable: "--io.sentry.sessionReplayQuality" + value: + isEnabled: false + - variable: "--io.sentry.tracesSampleRate" value: isEnabled: false diff --git a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift index be2f24a403..f4e66b592b 100644 --- a/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift +++ b/Samples/iOS-Swift/iOS-Swift/AppDelegate.swift @@ -16,10 +16,9 @@ class AppDelegate: UIResponder, UIApplicationDelegate { if args.contains("--io.sentry.wipe-data") { removeAppData() } - if !args.contains("--skip-sentry-init") { - SentrySDKWrapper.shared.startSentry() - } - + + SentrySDKWrapper.shared.startSentry() + if #available(iOS 15.0, *) { metricKit.receiveReports() } diff --git a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift index 41baa6e93d..e53e482359 100644 --- a/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift +++ b/Sources/Swift/Integrations/SessionReplay/SentryReplayOptions.swift @@ -40,7 +40,7 @@ public class SentryReplayOptions: NSObject, SentryRedactOptions { /** * Used by Hybrid SDKs. */ - static func fromName(_ name: String) -> SentryReplayOptions.SentryReplayQuality { + public static func fromName(_ name: String) -> SentryReplayOptions.SentryReplayQuality { switch name { case "low": return .low case "medium": return .medium diff --git a/scripts/build-sample-apps.sh b/scripts/build-sample-apps.sh new file mode 100755 index 0000000000..525a5f583f --- /dev/null +++ b/scripts/build-sample-apps.sh @@ -0,0 +1,87 @@ +#!/usr/bin/env bash + +set -x + +# Function to build a sample app +build_app() { + local xcodeproj=$1 + local scheme=$2 + local destination=$3 + local log_file="/tmp/${scheme}.log" + local derived_data_path="/tmp/${scheme}" + cmd="xcodebuild build -project \"${xcodeproj}\" -scheme \"${scheme}\" -destination \"${destination}\" -configuration Debug -derivedDataPath \"${derived_data_path}\"" + echo "$cmd" + if eval "${cmd}" > "${log_file}" 2>&1; then + echo "${xcodeproj}::${scheme}: success" + else + echo "${xcodeproj}::${scheme}: failed (see ${log_file})" + fi +} + +# Define an associative array with xcodeproj paths as keys and schemes as values +declare -A projects_and_schemes=( + ["iOS13-Swift"]="./Samples/iOS13-Swift/iOS13-Swift.xcodeproj" + ["iOS-SwiftUI"]="./Samples/iOS-SwiftUI/iOS-SwiftUI.xcodeproj" + ["iOS-Swift6"]="./Samples/iOS-Swift6/iOS-Swift6.xcodeproj" + ["iOS-Swift"]="./Samples/iOS-Swift/iOS-Swift.xcodeproj" + ["watchOS-Swift WatchKit App"]="./Samples/watchOS-Swift/watchOS-Swift.xcodeproj" + ["visionOS-Swift"]="./Samples/visionOS-Swift/visionOS-Swift.xcodeproj" + ["tvOS-Swift"]="./Samples/tvOS-Swift/tvOS-Swift.xcodeproj" + ["macOS-SwiftUI"]="./Samples/macOS-SwiftUI/macOS-SwiftUI.xcodeproj" + ["macOS-Swift"]="./Samples/macOS-Swift/macOS-Swift.xcodeproj" + ["macOS-Swift-Other"]="./Samples/macOS-Swift/macOS-Swift.xcodeproj" + ["macOS-Swift-Sandboxed"]="./Samples/macOS-Swift/macOS-Swift.xcodeproj" + ["macOS-Swift-Sandboxed-Other"]="./Samples/macOS-Swift/macOS-Swift.xcodeproj" + ["iOS15-SwiftUI"]="./Samples/iOS15-SwiftUI/iOS15-SwiftUI.xcodeproj" + ["iOS-ObjectiveC"]="./Samples/iOS-ObjectiveC/iOS-ObjectiveC.xcodeproj" +) + +resolve_destination_for_watch_pair() { + local sim_data + sim_data=$(xcrun simctl list -j) + + # Use jq to extract the latest available device pair + local pair_info + pair_info=$(echo "$sim_data" | jq -r ' + .pairs | to_entries[0].value | "\(.watch.udid) \(.phone.udid)"' | head -n1) + + local watch_udid + local phone_udid + read -r watch_udid phone_udid <<< "$pair_info" + + if [[ -z "$watch_udid" || -z "$phone_udid" ]]; then + echo "❌ Could not find matching simulators for a watch and phone pair." + return 1 + fi + + echo "platform=watchOS Simulator,id=$watch_udid,pairedWith=$phone_udid" +} + +watch_destination=$(resolve_destination_for_watch_pair) + +declare -A projects_and_destinations=( + ["iOS13-Swift"]="platform=iOS Simulator,OS=latest,name=iPhone 16" + ["iOS-SwiftUI"]="platform=iOS Simulator,OS=latest,name=iPhone 16" + ["iOS-Swift6"]="platform=iOS Simulator,OS=latest,name=iPhone 16" + ["iOS-Swift"]="platform=iOS Simulator,OS=latest,name=iPhone 16" + ["watchOS-Swift WatchKit App"]="$watch_destination" + ["visionOS-Swift"]="platform=visionOS Simulator,OS=latest,name=Apple Vision Pro" + ["tvOS-Swift"]="platform=tvOS Simulator,OS=latest,name=Apple TV" + ["macOS-SwiftUI"]="platform=macOS" + ["macOS-Swift"]="platform=macOS" + ["macOS-Swift-Other"]="platform=macOS" + ["macOS-Swift-Sandboxed"]="platform=macOS" + ["macOS-Swift-Sandboxed-Other"]="platform=macOS" + ["iOS15-SwiftUI"]="platform=iOS Simulator,OS=latest,name=iPhone 16" + ["iOS-ObjectiveC"]="platform=iOS Simulator,OS=latest,name=iPhone 16" +) + +# Iterate over the associative array and build each project with its scheme +for scheme in "${!projects_and_schemes[@]}"; do + xcodeproj="${projects_and_schemes[$scheme]}" + destination="${projects_and_destinations[$scheme]}" + build_app "$xcodeproj" "$scheme" "$destination" & +done + +# Wait for all background jobs to finish +wait