From a93240a7e76c736f9bae61324409bd23254c2bd7 Mon Sep 17 00:00:00 2001 From: Jakub Fiser <141125531+jfiser-paylocity@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:26:18 +0200 Subject: [PATCH 01/43] Add watchOS platform support to Core and Logs targets --- .../Context/ApplicationStatePublisher.swift | 27 +++++++----- .../Upload/BackgroundTaskCoordinator.swift | 2 + .../Sources/Core/Upload/FeatureUpload.swift | 4 ++ .../Core/Upload/URLSessionClient.swift | 2 + .../Sources/Kronos/KronosDNSResolver.swift | 6 +++ .../Sources/Context/AppState.swift | 10 ++++- .../Context/ApplicationNotifications.swift | 41 +++++++++++++++++++ .../Sources/Context/DeviceInfo.swift | 10 ++--- .../Sources/Context/DeviceProtocol.swift | 30 ++++++++++++++ DatadogInternal/Sources/DD.swift | 4 +- .../URLSession/URLSessionTask+Tracking.swift | 2 +- .../Sources/Utils/UIKitExtensions.swift | 2 +- 12 files changed, 120 insertions(+), 20 deletions(-) create mode 100644 DatadogInternal/Sources/Context/ApplicationNotifications.swift create mode 100644 DatadogInternal/Sources/Context/DeviceProtocol.swift diff --git a/DatadogCore/Sources/Core/Context/ApplicationStatePublisher.swift b/DatadogCore/Sources/Core/Context/ApplicationStatePublisher.swift index f6c86d18b5..c8fcb64298 100644 --- a/DatadogCore/Sources/Core/Context/ApplicationStatePublisher.swift +++ b/DatadogCore/Sources/Core/Context/ApplicationStatePublisher.swift @@ -7,12 +7,19 @@ #if canImport(UIKit) import UIKit import DatadogInternal +#if canImport(WatchKit) +import WatchKit +#endif internal final class ApplicationStatePublisher: ContextValuePublisher { typealias Snapshot = AppStateHistory.Snapshot - private static var currentApplicationState: UIApplication.State { + private static var currentApplicationState: ApplicationState { + #if canImport(WatchKit) + WKExtension.shared().applicationState + #else UIApplication.dd.managedShared?.applicationState ?? .active // fallback to most expected state + #endif } /// The default publisher queue. @@ -85,7 +92,7 @@ internal final class ApplicationStatePublisher: ContextValuePublisher { /// - dateProvider: The date provider for the Application state snapshot timestamp. /// - notificationCenter: The notification center where this publisher observes `UIApplication` notifications. convenience init( - applicationState: UIApplication.State = ApplicationStatePublisher.currentApplicationState, + applicationState: ApplicationState = ApplicationStatePublisher.currentApplicationState, queue: DispatchQueue = ApplicationStatePublisher.defaultQueue, dateProvider: DateProvider = SystemDateProvider(), notificationCenter: NotificationCenter = .default @@ -100,10 +107,10 @@ internal final class ApplicationStatePublisher: ContextValuePublisher { func publish(to receiver: @escaping ContextValueReceiver) { queue.async { self.receiver = receiver } - notificationCenter.addObserver(self, selector: #selector(applicationDidBecomeActive), name: UIApplication.didBecomeActiveNotification, object: nil) - notificationCenter.addObserver(self, selector: #selector(applicationWillResignActive), name: UIApplication.willResignActiveNotification, object: nil) - notificationCenter.addObserver(self, selector: #selector(applicationDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil) - notificationCenter.addObserver(self, selector: #selector(applicationWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil) + notificationCenter.addObserver(self, selector: #selector(applicationDidBecomeActive), name: ApplicationNotifications.didBecomeActive, object: nil) + notificationCenter.addObserver(self, selector: #selector(applicationWillResignActive), name: ApplicationNotifications.willResignActive, object: nil) + notificationCenter.addObserver(self, selector: #selector(applicationDidEnterBackground), name: ApplicationNotifications.didEnterBackground, object: nil) + notificationCenter.addObserver(self, selector: #selector(applicationWillEnterForeground), name: ApplicationNotifications.willEnterForeground, object: nil) } @objc @@ -135,10 +142,10 @@ internal final class ApplicationStatePublisher: ContextValuePublisher { } func cancel() { - notificationCenter.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) - notificationCenter.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) - notificationCenter.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil) - notificationCenter.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil) + notificationCenter.removeObserver(self, name: ApplicationNotifications.didBecomeActive, object: nil) + notificationCenter.removeObserver(self, name: ApplicationNotifications.willResignActive, object: nil) + notificationCenter.removeObserver(self, name: ApplicationNotifications.didEnterBackground, object: nil) + notificationCenter.removeObserver(self, name: ApplicationNotifications.willEnterForeground, object: nil) queue.async { self.receiver = nil } } } diff --git a/DatadogCore/Sources/Core/Upload/BackgroundTaskCoordinator.swift b/DatadogCore/Sources/Core/Upload/BackgroundTaskCoordinator.swift index c6c417a9df..5ddba5f8b7 100644 --- a/DatadogCore/Sources/Core/Upload/BackgroundTaskCoordinator.swift +++ b/DatadogCore/Sources/Core/Upload/BackgroundTaskCoordinator.swift @@ -20,6 +20,7 @@ internal protocol BackgroundTaskCoordinator { import UIKit import DatadogInternal +#if !os(watchOS) /// Bridge protocol that calls corresponding `UIApplication` interface for background tasks. Allows easier testablity. internal protocol UIKitAppBackgroundTaskCoordinator { func beginBgTask(_ handler: (() -> Void)?) -> UIBackgroundTaskIdentifier @@ -69,6 +70,7 @@ internal class AppBackgroundTaskCoordinator: BackgroundTaskCoordinator { self.currentTaskId = nil } } +#endif /// Bridge protocol that matches `ProcessInfo` interface for background activity. Allows easier testablity. internal protocol ProcessInfoActivityCoordinator { diff --git a/DatadogCore/Sources/Core/Upload/FeatureUpload.swift b/DatadogCore/Sources/Core/Upload/FeatureUpload.swift index dcdd25d1a8..766f5618e2 100644 --- a/DatadogCore/Sources/Core/Upload/FeatureUpload.swift +++ b/DatadogCore/Sources/Core/Upload/FeatureUpload.swift @@ -38,7 +38,11 @@ internal struct FeatureUpload { let backgroundTaskCoordinator: BackgroundTaskCoordinator? switch (backgroundTasksEnabled, isRunFromExtension) { case (true, false): + #if os(watchOS) + backgroundTaskCoordinator = nil + #else backgroundTaskCoordinator = AppBackgroundTaskCoordinator() + #endif case (true, true): backgroundTaskCoordinator = ExtensionBackgroundTaskCoordinator() case (false, _): diff --git a/DatadogCore/Sources/Core/Upload/URLSessionClient.swift b/DatadogCore/Sources/Core/Upload/URLSessionClient.swift index 1cf66dd5d9..0523a44af9 100644 --- a/DatadogCore/Sources/Core/Upload/URLSessionClient.swift +++ b/DatadogCore/Sources/Core/Upload/URLSessionClient.swift @@ -18,6 +18,7 @@ internal class URLSessionClient: HTTPClient { configuration.urlCache = nil configuration.connectionProxyDictionary = proxyConfiguration + #if !os(watchOS) // URLSession does not set the `Proxy-Authorization` header automatically when using a proxy // configuration. We manually set the HTTP basic authentication header. if let user = proxyConfiguration?[kCFProxyUsernameKey] as? String, @@ -25,6 +26,7 @@ internal class URLSessionClient: HTTPClient { let authorization = basicHTTPAuthentication(username: user, password: password) configuration.httpAdditionalHeaders = ["Proxy-Authorization": authorization] } + #endif self.init(session: URLSession(configuration: configuration)) } diff --git a/DatadogCore/Sources/Kronos/KronosDNSResolver.swift b/DatadogCore/Sources/Kronos/KronosDNSResolver.swift index 4d8ca1e40f..5b6dfe9819 100644 --- a/DatadogCore/Sources/Kronos/KronosDNSResolver.swift +++ b/DatadogCore/Sources/Kronos/KronosDNSResolver.swift @@ -30,6 +30,9 @@ internal final class KronosDNSResolver { timeout: TimeInterval = kDefaultTimeout, completion: @escaping ([KronosInternetAddress]) -> Void ) { + #if os(watchOS) + completion([]) + #else let callback: CFHostClientCallBack = { host, _, _, info in guard let info = info else { return @@ -79,8 +82,10 @@ internal final class KronosDNSResolver { CFHostSetClient(hostReference, callback, &clientContext) CFHostScheduleWithRunLoop(hostReference, CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue) CFHostStartInfoResolution(hostReference, .addresses, nil) + #endif } + #if !os(watchOS) @objc private func onTimeout() { defer { @@ -99,4 +104,5 @@ internal final class KronosDNSResolver { CFHostUnscheduleFromRunLoop(hostReference, CFRunLoopGetMain(), CFRunLoopMode.commonModes.rawValue) CFHostSetClient(hostReference, nil, nil) } + #endif } diff --git a/DatadogInternal/Sources/Context/AppState.swift b/DatadogInternal/Sources/Context/AppState.swift index 12b4c4b5d6..79923d6118 100644 --- a/DatadogInternal/Sources/Context/AppState.swift +++ b/DatadogInternal/Sources/Context/AppState.swift @@ -146,8 +146,16 @@ extension AppStateHistory { import UIKit +#if canImport(WatchKit) +import WatchKit + +public typealias ApplicationState = WKApplicationState +#else +public typealias ApplicationState = UIApplication.State +#endif + extension AppState { - public init(_ state: UIApplication.State) { + public init(_ state: ApplicationState) { switch state { case .active: self = .active diff --git a/DatadogInternal/Sources/Context/ApplicationNotifications.swift b/DatadogInternal/Sources/Context/ApplicationNotifications.swift new file mode 100644 index 0000000000..9c4da0c4d2 --- /dev/null +++ b/DatadogInternal/Sources/Context/ApplicationNotifications.swift @@ -0,0 +1,41 @@ +#if canImport(UIKit) +import UIKit +#if canImport(WatchKit) +import WatchKit +#endif + +/// Convenient wrapper to get system notifications independent from platform +public enum ApplicationNotifications { + public static var didBecomeActive: Notification.Name { + #if canImport(WatchKit) + WKExtension.applicationDidBecomeActiveNotification + #else + UIApplication.didBecomeActiveNotification + #endif + } + + public static var willResignActive: Notification.Name { + #if canImport(WatchKit) + WKExtension.applicationWillResignActiveNotification + #else + UIApplication.willResignActiveNotification + #endif + } + + public static var didEnterBackground: Notification.Name { + #if canImport(WatchKit) + WKExtension.applicationDidEnterBackgroundNotification + #else + UIApplication.didEnterBackgroundNotification + #endif + } + + public static var willEnterForeground: Notification.Name { + #if canImport(WatchKit) + WKExtension.applicationWillEnterForegroundNotification + #else + UIApplication.willEnterForegroundNotification + #endif + } +} +#endif diff --git a/DatadogInternal/Sources/Context/DeviceInfo.swift b/DatadogInternal/Sources/Context/DeviceInfo.swift index 46583079a7..b24c453046 100644 --- a/DatadogInternal/Sources/Context/DeviceInfo.swift +++ b/DatadogInternal/Sources/Context/DeviceInfo.swift @@ -75,14 +75,14 @@ import MachO import UIKit extension DeviceInfo { - /// Creates device info based on UIKit description. + /// Creates device info based on device description. /// /// - Parameters: /// - processInfo: The current process information. - /// - device: The `UIDevice` description. + /// - device: The `DeviceProtocol` description. public init( processInfo: ProcessInfo = .processInfo, - device: UIDevice = .current, + device: DeviceProtocol = DeviceFactory.current, sysctl: SysctlProviding = Sysctl() ) { var architecture = "unknown" @@ -96,7 +96,7 @@ extension DeviceInfo { #if !targetEnvironment(simulator) let model = try? sysctl.model() - // Real iOS device + // Real device self.init( name: device.model, model: model ?? device.model, @@ -111,7 +111,7 @@ extension DeviceInfo { ) #else let model = processInfo.environment["SIMULATOR_MODEL_IDENTIFIER"] ?? device.model - // iOS Simulator - battery monitoring doesn't work on Simulator, so return "always OK" value + // Simulator - battery monitoring doesn't work on Simulator, so return "always OK" value self.init( name: device.model, model: "\(model) Simulator", diff --git a/DatadogInternal/Sources/Context/DeviceProtocol.swift b/DatadogInternal/Sources/Context/DeviceProtocol.swift new file mode 100644 index 0000000000..42020cfad5 --- /dev/null +++ b/DatadogInternal/Sources/Context/DeviceProtocol.swift @@ -0,0 +1,30 @@ +#if canImport(UIKit) +import UIKit + +/// Abstraction protocol to get device information on the current platform +public protocol DeviceProtocol { + var model: String { get } + var systemName: String { get } + var systemVersion: String { get } + var identifierForVendor: UUID? { get } +} + +#if canImport(WatchKit) +import WatchKit + +extension WKInterfaceDevice: DeviceProtocol {} +#else +extension UIDevice: DeviceProtocol {} +#endif + +public enum DeviceFactory { + /// Get the current device + public static var current: DeviceProtocol { + #if canImport(WatchKit) + WKInterfaceDevice.current() + #else + UIDevice.current + #endif + } +} +#endif diff --git a/DatadogInternal/Sources/DD.swift b/DatadogInternal/Sources/DD.swift index fd10e21633..1ecc964359 100644 --- a/DatadogInternal/Sources/DD.swift +++ b/DatadogInternal/Sources/DD.swift @@ -32,7 +32,7 @@ import OSLog /// Function printing `String` content to console. public var consolePrint: (String, CoreLoggerLevel) -> Void = { message, level in #if canImport(OSLog) - if #available(iOS 14.0, tvOS 14.0, *) { + if #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) { switch level { case .debug: Logger.datadog.debug("\(message, privacy: .private)") case .warn: Logger.datadog.warning("\(message, privacy: .private)") @@ -48,7 +48,7 @@ public var consolePrint: (String, CoreLoggerLevel) -> Void = { message, level in } #if canImport(OSLog) -@available(iOS 14.0, tvOS 14.0, *) +@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) extension Logger { static let datadog = Logger(subsystem: "dd-sdk-ios", category: "DatadogInternal") } diff --git a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift index da1a44f42c..71b0c66f0e 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/URLSession/URLSessionTask+Tracking.swift @@ -21,7 +21,7 @@ extension DatadogExtension where ExtendedType: URLSessionTask { /// Returns the delegate instance the task is reporting to. var delegate: URLSessionDelegate? { - if #available(iOS 15.0, tvOS 15.0, *), let delegate = type.delegate { + if #available(iOS 15.0, tvOS 15.0, watchOS 8.0, *), let delegate = type.delegate { return delegate } diff --git a/DatadogInternal/Sources/Utils/UIKitExtensions.swift b/DatadogInternal/Sources/Utils/UIKitExtensions.swift index c484916cc2..c9984f3dc6 100644 --- a/DatadogInternal/Sources/Utils/UIKitExtensions.swift +++ b/DatadogInternal/Sources/Utils/UIKitExtensions.swift @@ -6,7 +6,7 @@ import Foundation -#if canImport(UIKit) +#if canImport(UIKit) && !os(watchOS) import UIKit extension DatadogExtension where ExtendedType == UIApplication { From 9b488046bb1b1e61c9e5d0beaba0f7909c71bcc9 Mon Sep 17 00:00:00 2001 From: Jakub Fiser <141125531+jfiser-paylocity@users.noreply.github.com> Date: Fri, 21 Jun 2024 14:32:39 +0200 Subject: [PATCH 02/43] Resolve minimum watchOS version in DeviceProtocol --- DatadogInternal/Sources/Context/DeviceInfo.swift | 2 +- .../Sources/Context/DeviceProtocol.swift | 16 +++++++++++++--- 2 files changed, 14 insertions(+), 4 deletions(-) diff --git a/DatadogInternal/Sources/Context/DeviceInfo.swift b/DatadogInternal/Sources/Context/DeviceInfo.swift index b24c453046..949026a04f 100644 --- a/DatadogInternal/Sources/Context/DeviceInfo.swift +++ b/DatadogInternal/Sources/Context/DeviceInfo.swift @@ -120,7 +120,7 @@ extension DeviceInfo { osBuildNumber: build, architecture: architecture, isSimulator: true, - vendorId: device.identifierForVendor?.uuidString, + vendorId: device.vendorIdentifier?.uuidString, isDebugging: isDebugging ?? false, systemBootTime: systemBootTime ?? Date.timeIntervalSinceReferenceDate ) diff --git a/DatadogInternal/Sources/Context/DeviceProtocol.swift b/DatadogInternal/Sources/Context/DeviceProtocol.swift index 42020cfad5..e4f0258ae0 100644 --- a/DatadogInternal/Sources/Context/DeviceProtocol.swift +++ b/DatadogInternal/Sources/Context/DeviceProtocol.swift @@ -6,15 +6,25 @@ public protocol DeviceProtocol { var model: String { get } var systemName: String { get } var systemVersion: String { get } - var identifierForVendor: UUID? { get } + var vendorIdentifier: UUID? { get } } #if canImport(WatchKit) import WatchKit -extension WKInterfaceDevice: DeviceProtocol {} +extension WKInterfaceDevice: DeviceProtocol { + public var vendorIdentifier: UUID? { + if #available(watchOS 6.2, *) { + identifierForVendor + } else { + nil + } + } +} #else -extension UIDevice: DeviceProtocol {} +extension UIDevice: DeviceProtocol { + public var vendorIdentifier: UUID? { identifierForVendor } +} #endif public enum DeviceFactory { From 47591ce07204e4045f8720073b1f0c315a2a6987 Mon Sep 17 00:00:00 2001 From: Jakub Fiser <141125531+jfiser-paylocity@users.noreply.github.com> Date: Fri, 21 Jun 2024 15:27:09 +0200 Subject: [PATCH 03/43] Require minimum watchOS version due to missing ApplicationNotifications --- DatadogCore.podspec | 1 + DatadogInternal.podspec | 1 + .../Context/ApplicationNotifications.swift | 8 ++++++ .../Sources/Context/DeviceInfo.swift | 2 +- .../Sources/Context/DeviceProtocol.swift | 28 +++++++++---------- DatadogInternal/Sources/DD.swift | 4 +-- DatadogLogs.podspec | 1 + Package.swift | 3 +- xcconfigs/Base.xcconfig | 1 + 9 files changed, 30 insertions(+), 19 deletions(-) diff --git a/DatadogCore.podspec b/DatadogCore.podspec index 36bdcc9523..02085c172d 100644 --- a/DatadogCore.podspec +++ b/DatadogCore.podspec @@ -17,6 +17,7 @@ Pod::Spec.new do |s| s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' + s.watchos.deployment_target = '7.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogInternal.podspec b/DatadogInternal.podspec index 46048a1330..6441edf602 100644 --- a/DatadogInternal.podspec +++ b/DatadogInternal.podspec @@ -17,6 +17,7 @@ Pod::Spec.new do |s| s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' + s.watchos.deployment_target = '7.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/DatadogInternal/Sources/Context/ApplicationNotifications.swift b/DatadogInternal/Sources/Context/ApplicationNotifications.swift index 9c4da0c4d2..43af3abf8a 100644 --- a/DatadogInternal/Sources/Context/ApplicationNotifications.swift +++ b/DatadogInternal/Sources/Context/ApplicationNotifications.swift @@ -1,3 +1,11 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation + #if canImport(UIKit) import UIKit #if canImport(WatchKit) diff --git a/DatadogInternal/Sources/Context/DeviceInfo.swift b/DatadogInternal/Sources/Context/DeviceInfo.swift index 949026a04f..b24c453046 100644 --- a/DatadogInternal/Sources/Context/DeviceInfo.swift +++ b/DatadogInternal/Sources/Context/DeviceInfo.swift @@ -120,7 +120,7 @@ extension DeviceInfo { osBuildNumber: build, architecture: architecture, isSimulator: true, - vendorId: device.vendorIdentifier?.uuidString, + vendorId: device.identifierForVendor?.uuidString, isDebugging: isDebugging ?? false, systemBootTime: systemBootTime ?? Date.timeIntervalSinceReferenceDate ) diff --git a/DatadogInternal/Sources/Context/DeviceProtocol.swift b/DatadogInternal/Sources/Context/DeviceProtocol.swift index e4f0258ae0..a6b71a695a 100644 --- a/DatadogInternal/Sources/Context/DeviceProtocol.swift +++ b/DatadogInternal/Sources/Context/DeviceProtocol.swift @@ -1,30 +1,28 @@ -#if canImport(UIKit) -import UIKit +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation /// Abstraction protocol to get device information on the current platform public protocol DeviceProtocol { var model: String { get } var systemName: String { get } var systemVersion: String { get } - var vendorIdentifier: UUID? { get } + var identifierForVendor: UUID? { get } } +#if canImport(UIKit) +import UIKit + #if canImport(WatchKit) import WatchKit -extension WKInterfaceDevice: DeviceProtocol { - public var vendorIdentifier: UUID? { - if #available(watchOS 6.2, *) { - identifierForVendor - } else { - nil - } - } -} +extension WKInterfaceDevice: DeviceProtocol {} #else -extension UIDevice: DeviceProtocol { - public var vendorIdentifier: UUID? { identifierForVendor } -} +extension UIDevice: DeviceProtocol {} #endif public enum DeviceFactory { diff --git a/DatadogInternal/Sources/DD.swift b/DatadogInternal/Sources/DD.swift index 1ecc964359..fd10e21633 100644 --- a/DatadogInternal/Sources/DD.swift +++ b/DatadogInternal/Sources/DD.swift @@ -32,7 +32,7 @@ import OSLog /// Function printing `String` content to console. public var consolePrint: (String, CoreLoggerLevel) -> Void = { message, level in #if canImport(OSLog) - if #available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) { + if #available(iOS 14.0, tvOS 14.0, *) { switch level { case .debug: Logger.datadog.debug("\(message, privacy: .private)") case .warn: Logger.datadog.warning("\(message, privacy: .private)") @@ -48,7 +48,7 @@ public var consolePrint: (String, CoreLoggerLevel) -> Void = { message, level in } #if canImport(OSLog) -@available(iOS 14.0, tvOS 14.0, watchOS 7.0, *) +@available(iOS 14.0, tvOS 14.0, *) extension Logger { static let datadog = Logger(subsystem: "dd-sdk-ios", category: "DatadogInternal") } diff --git a/DatadogLogs.podspec b/DatadogLogs.podspec index 110ed51c2a..56d24517fc 100644 --- a/DatadogLogs.podspec +++ b/DatadogLogs.podspec @@ -17,6 +17,7 @@ Pod::Spec.new do |s| s.swift_version = '5.9' s.ios.deployment_target = '12.0' s.tvos.deployment_target = '12.0' + s.watchos.deployment_target = '7.0' s.source = { :git => "https://github.com/DataDog/dd-sdk-ios.git", :tag => s.version.to_s } diff --git a/Package.swift b/Package.swift index e62f4f19d7..8aa585e4d5 100644 --- a/Package.swift +++ b/Package.swift @@ -8,7 +8,8 @@ let package = Package( platforms: [ .iOS(.v12), .tvOS(.v12), - .macOS(.v12) + .macOS(.v12), + .watchOS(.v7) ], products: [ .library( diff --git a/xcconfigs/Base.xcconfig b/xcconfigs/Base.xcconfig index ad4f975149..e8f6264232 100644 --- a/xcconfigs/Base.xcconfig +++ b/xcconfigs/Base.xcconfig @@ -11,6 +11,7 @@ ARCHS[sdk=iphoneos*]=$(ARCHS_STANDARD) arm64e IPHONEOS_DEPLOYMENT_TARGET=12.0 TVOS_DEPLOYMENT_TARGET=12.0 MACOSX_DEPLOYMENT_TARGET=12.6 +WATCHOS_DEPLOYMENT_TARGET=7.0 // Minimum supported Swift version SWIFT_VERSION=5.9 From 560b424320fe4cb588ac2ce83e718ccdd0ed3a6f Mon Sep 17 00:00:00 2001 From: Jakub Fiser <141125531+jfiser-paylocity@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:09:37 +0200 Subject: [PATCH 04/43] Use dd accessor on watch --- .../Context/ApplicationStatePublisher.swift | 2 +- .../Sources/Utils/WatchKitExtensions.swift | 21 +++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) create mode 100644 DatadogInternal/Sources/Utils/WatchKitExtensions.swift diff --git a/DatadogCore/Sources/Core/Context/ApplicationStatePublisher.swift b/DatadogCore/Sources/Core/Context/ApplicationStatePublisher.swift index c8fcb64298..e6b1096797 100644 --- a/DatadogCore/Sources/Core/Context/ApplicationStatePublisher.swift +++ b/DatadogCore/Sources/Core/Context/ApplicationStatePublisher.swift @@ -16,7 +16,7 @@ internal final class ApplicationStatePublisher: ContextValuePublisher { private static var currentApplicationState: ApplicationState { #if canImport(WatchKit) - WKExtension.shared().applicationState + WKExtension.dd.shared.applicationState #else UIApplication.dd.managedShared?.applicationState ?? .active // fallback to most expected state #endif diff --git a/DatadogInternal/Sources/Utils/WatchKitExtensions.swift b/DatadogInternal/Sources/Utils/WatchKitExtensions.swift new file mode 100644 index 0000000000..a91cfb58e5 --- /dev/null +++ b/DatadogInternal/Sources/Utils/WatchKitExtensions.swift @@ -0,0 +1,21 @@ +// +// WatchKitExtensions.swift +// Datadog +// +// Created by Jakub Fiser on 26.06.2024. +// Copyright © 2024 Datadog. All rights reserved. +// + +import Foundation + +#if canImport(WatchKit) +import WatchKit + +extension DatadogExtension where ExtendedType == WKExtension { + public static var shared: WKExtension { + .shared() + } +} + +extension WKExtension: DatadogExtended { } +#endif From d47fcfa52acc26c0886eb6b00027824c4d7ac1c4 Mon Sep 17 00:00:00 2001 From: Jakub Fiser <141125531+jfiser-paylocity@users.noreply.github.com> Date: Wed, 26 Jun 2024 10:10:33 +0200 Subject: [PATCH 05/43] Use process background task on watch --- DatadogCore/Sources/Core/Upload/FeatureUpload.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DatadogCore/Sources/Core/Upload/FeatureUpload.swift b/DatadogCore/Sources/Core/Upload/FeatureUpload.swift index 766f5618e2..5d6f4304cc 100644 --- a/DatadogCore/Sources/Core/Upload/FeatureUpload.swift +++ b/DatadogCore/Sources/Core/Upload/FeatureUpload.swift @@ -39,7 +39,7 @@ internal struct FeatureUpload { switch (backgroundTasksEnabled, isRunFromExtension) { case (true, false): #if os(watchOS) - backgroundTaskCoordinator = nil + backgroundTaskCoordinator = ExtensionBackgroundTaskCoordinator() #else backgroundTaskCoordinator = AppBackgroundTaskCoordinator() #endif From a9549d17e0d0d7231e5107f0c218133c38ff79e2 Mon Sep 17 00:00:00 2001 From: Jakub Fiser <141125531+jfiser-paylocity@users.noreply.github.com> Date: Thu, 27 Jun 2024 15:05:39 +0200 Subject: [PATCH 06/43] Replace personal file header with the prefefined one --- .../Sources/Utils/WatchKitExtensions.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/DatadogInternal/Sources/Utils/WatchKitExtensions.swift b/DatadogInternal/Sources/Utils/WatchKitExtensions.swift index a91cfb58e5..8b08c2267d 100644 --- a/DatadogInternal/Sources/Utils/WatchKitExtensions.swift +++ b/DatadogInternal/Sources/Utils/WatchKitExtensions.swift @@ -1,10 +1,8 @@ -// -// WatchKitExtensions.swift -// Datadog -// -// Created by Jakub Fiser on 26.06.2024. -// Copyright © 2024 Datadog. All rights reserved. -// +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ import Foundation From d0df86e18fb184862f978f06f355008eef46cacb Mon Sep 17 00:00:00 2001 From: Jakub Fiser <141125531+jfiser-paylocity@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:17:12 +0200 Subject: [PATCH 07/43] Add new files in the project --- Datadog/Datadog.xcodeproj/project.pbxproj | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index f769710802..6bfa5847b0 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -1680,6 +1680,12 @@ E1C853142AA9B9A300C74BCF /* TelemetryMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C853132AA9B9A300C74BCF /* TelemetryMocks.swift */; }; E1C853152AA9B9A300C74BCF /* TelemetryMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C853132AA9B9A300C74BCF /* TelemetryMocks.swift */; }; E1D5AEA724B4D45B007F194B /* Versioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D5AEA624B4D45A007F194B /* Versioning.swift */; }; + E2AA55E42C32C6AF002FEF28 /* DeviceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E32C32C6AF002FEF28 /* DeviceProtocol.swift */; }; + E2AA55E52C32C6AF002FEF28 /* DeviceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E32C32C6AF002FEF28 /* DeviceProtocol.swift */; }; + E2AA55E72C32C6D9002FEF28 /* ApplicationNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */; }; + E2AA55E82C32C6D9002FEF28 /* ApplicationNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */; }; + E2AA55EA2C32C76A002FEF28 /* WatchKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */; }; + E2AA55EC2C32C78B002FEF28 /* WatchKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */; }; /* End PBXBuildFile section */ /* Begin PBXContainerItemProxy section */ @@ -3026,6 +3032,9 @@ E1D202E924C065CF00D1AF3A /* ActiveSpansPool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSpansPool.swift; sourceTree = ""; }; E1D203FB24C1884500D1AF3A /* ActiveSpansPoolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSpansPoolTests.swift; sourceTree = ""; }; E1D5AEA624B4D45A007F194B /* Versioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Versioning.swift; sourceTree = ""; }; + E2AA55E32C32C6AF002FEF28 /* DeviceProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceProtocol.swift; sourceTree = ""; }; + E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationNotifications.swift; sourceTree = ""; }; + E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchKitExtensions.swift; sourceTree = ""; }; F637AED12697404200516F32 /* UIKitRUMUserActionsPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitRUMUserActionsPredicate.swift; sourceTree = ""; }; /* End PBXFileReference section */ @@ -5813,6 +5822,7 @@ D23039B2298D5235001A1FA3 /* Context */ = { isa = PBXGroup; children = ( + E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */, D23039B3298D5235001A1FA3 /* AppState.swift */, D23039B4298D5235001A1FA3 /* UserInfo.swift */, D23039B5298D5235001A1FA3 /* BatteryStatus.swift */, @@ -5823,6 +5833,7 @@ D23039BA298D5235001A1FA3 /* DatadogContext.swift */, D23039BB298D5235001A1FA3 /* TrackingConsent.swift */, D23039BC298D5235001A1FA3 /* DeviceInfo.swift */, + E2AA55E32C32C6AF002FEF28 /* DeviceProtocol.swift */, D23039BE298D5235001A1FA3 /* LaunchTime.swift */, D2F8235229915E12003C7E99 /* DatadogSite.swift */, 6174D6122BFDF16C00EC7469 /* BundleType.swift */, @@ -6256,6 +6267,7 @@ D23039D9298D5235001A1FA3 /* DateFormatting.swift */, 613C6B8F2768FDDE00870CBF /* Sampler.swift */, D23039DC298D5235001A1FA3 /* DDError.swift */, + E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */, 61133BBA2423979B00786299 /* SwiftExtensions.swift */, D29A9F9429DDB1DB005C54A4 /* UIKitExtensions.swift */, ); @@ -8576,6 +8588,7 @@ D23039F9298D5236001A1FA3 /* CoreLogger.swift in Sources */, D2160CA229C0DE5700FAA9A5 /* NetworkInstrumentationFeature.swift in Sources */, D2EBEE1F29BA160F00B15732 /* HTTPHeadersReader.swift in Sources */, + E2AA55E72C32C6D9002FEF28 /* ApplicationNotifications.swift in Sources */, D263BCAF29DAFFEB00FA0E21 /* PerformancePresetOverride.swift in Sources */, D23039E7298D5236001A1FA3 /* NetworkConnectionInfo.swift in Sources */, D23039E9298D5236001A1FA3 /* TrackingConsent.swift in Sources */, @@ -8589,6 +8602,7 @@ D2160CC929C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift in Sources */, D2EBEE2929BA160F00B15732 /* HTTPHeadersWriter.swift in Sources */, D2432CF929EDB22C00D93657 /* Flushable.swift in Sources */, + E2AA55E42C32C6AF002FEF28 /* DeviceProtocol.swift in Sources */, D23039F7298D5236001A1FA3 /* AttributesSanitizer.swift in Sources */, D23039EB298D5236001A1FA3 /* DatadogFeature.swift in Sources */, D2BEEDBA2B33638F0065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */, @@ -8621,6 +8635,7 @@ D2160CF429C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */, D23039E1298D5236001A1FA3 /* AppState.swift in Sources */, D2DE63532A30A7CA00441A54 /* CoreRegistry.swift in Sources */, + E2AA55EA2C32C76A002FEF28 /* WatchKitExtensions.swift in Sources */, D2EBEE2829BA160F00B15732 /* W3CHTTPHeadersWriter.swift in Sources */, D23039EA298D5236001A1FA3 /* DeviceInfo.swift in Sources */, D2EBEE2329BA160F00B15732 /* B3HTTPHeadersReader.swift in Sources */, @@ -9543,6 +9558,7 @@ D2DA2358298D57AA00C6C7E6 /* CoreLogger.swift in Sources */, D2160CA329C0DE5700FAA9A5 /* NetworkInstrumentationFeature.swift in Sources */, D2EBEE2D29BA161100B15732 /* HTTPHeadersReader.swift in Sources */, + E2AA55E82C32C6D9002FEF28 /* ApplicationNotifications.swift in Sources */, D263BCB029DAFFEB00FA0E21 /* PerformancePresetOverride.swift in Sources */, D2DA2359298D57AA00C6C7E6 /* NetworkConnectionInfo.swift in Sources */, D2DA235A298D57AA00C6C7E6 /* TrackingConsent.swift in Sources */, @@ -9556,6 +9572,7 @@ D2160CCA29C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift in Sources */, D2EBEE3729BA161100B15732 /* HTTPHeadersWriter.swift in Sources */, D2432CFA29EDB22C00D93657 /* Flushable.swift in Sources */, + E2AA55E52C32C6AF002FEF28 /* DeviceProtocol.swift in Sources */, D2DA235D298D57AA00C6C7E6 /* AttributesSanitizer.swift in Sources */, D2DA235E298D57AA00C6C7E6 /* DatadogFeature.swift in Sources */, D2BEEDBB2B3363900065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */, @@ -9588,6 +9605,7 @@ D2160CF529C0EDFC00FAA9A5 /* UploadPerformancePreset.swift in Sources */, D2DA236C298D57AA00C6C7E6 /* AppState.swift in Sources */, D2DE63542A30A7CA00441A54 /* CoreRegistry.swift in Sources */, + E2AA55EC2C32C78B002FEF28 /* WatchKitExtensions.swift in Sources */, D2EBEE3629BA161100B15732 /* W3CHTTPHeadersWriter.swift in Sources */, D2DA236D298D57AA00C6C7E6 /* DeviceInfo.swift in Sources */, D2EBEE3129BA161100B15732 /* B3HTTPHeadersReader.swift in Sources */, From d9749fd1ed39aadcf490b10f578ec02c22a33f92 Mon Sep 17 00:00:00 2001 From: Jakub Fiser <141125531+jfiser-paylocity@users.noreply.github.com> Date: Mon, 1 Jul 2024 13:46:15 +0200 Subject: [PATCH 08/43] Enable smoke tests for watchOS platform --- .gitlab-ci.yml | 13 +++++++++++++ Makefile | 8 +++++++- tools/runner-setup.sh | 17 +++++++++++++++-- 3 files changed, 35 insertions(+), 3 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index df5e5f4923..1e734896e1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -181,3 +181,16 @@ Smoke Tests (macOS): - ./tools/runner-setup.sh --xcode "$XCODE" # temporary, waiting for AMI - make clean repo-setup ENV=ci - make spm-build-macos + +Smoke Tests (watchOS): + stage: smoke-test + tags: + - macos:ventura + - specific:true + variables: + XCODE: "15.2.0" + OS: "10.2" + script: + - ./tools/runner-setup.sh --xcode "$XCODE" --watchOS --os "$OS" # temporary, waiting for AMI + - make clean repo-setup ENV=ci + - make spm-build-watchos diff --git a/Makefile b/Makefile index c63c0b4561..11c5aade9e 100644 --- a/Makefile +++ b/Makefile @@ -5,7 +5,7 @@ all: env-check repo-setup templates ui-test ui-test-all ui-test-podinstall \ tools-test \ smoke-test smoke-test-ios smoke-test-ios-all smoke-test-tvos smoke-test-tvos-all \ - spm-build spm-build-ios spm-build-tvos spm-build-visionos spm-build-macos \ + spm-build spm-build-ios spm-build-tvos spm-build-visionos spm-build-macos spm-build-watchos \ models-generate rum-models-generate sr-models-generate models-verify rum-models-verify sr-models-verify \ REPO_ROOT := $(PWD) @@ -230,6 +230,12 @@ spm-build-tvos: spm-build-visionos: @$(MAKE) spm-build SCHEME="Datadog-Package" DESTINATION="generic/platform=visionOS" +# Builds SPM package for watchOS +spm-build-watchos: + # Build only compatible schemes for watchOS: + @$(MAKE) spm-build DESTINATION="generic/platform=watchOS" SCHEME="DatadogCore" + @$(MAKE) spm-build DESTINATION="generic/platform=watchOS" SCHEME="DatadogLogs" + # Builds SPM package for macOS (and Mac Catalyst) spm-build-macos: # Whole package for Mac Catalyst: diff --git a/tools/runner-setup.sh b/tools/runner-setup.sh index fd4e391bc0..42fb0bd032 100755 --- a/tools/runner-setup.sh +++ b/tools/runner-setup.sh @@ -9,7 +9,8 @@ # --iOS: Flag that prepares the runner instance for iOS testing. Disabled by default. # --tvOS: Flag that prepares the runner instance for tvOS testing. Disabled by default. # --visionOS: Flag that prepares the runner instance for visionOS testing. Disabled by default. -# --os: Sets the expected OS version for installed simulators when --iOS, --tvOS or --visionOS flag is set. Default: '17.4'. +# --watchOS: Flag that prepares the runner instance for watchOS testing. Disabled by default. +# --os: Sets the expected OS version for installed simulators when --iOS, --tvOS, --visionOS or --watchOS flag is set. Default: '17.4'. set -eo pipefail source ./tools/utils/echo_color.sh @@ -20,7 +21,8 @@ define_arg "xcode" "" "Sets the Xcode version on the runner." "string" "false" define_arg "iOS" "false" "Flag that prepares the runner instance for iOS testing. Disabled by default." "store_true" define_arg "tvOS" "false" "Flag that prepares the runner instance for tvOS testing. Disabled by default." "store_true" define_arg "visionOS" "false" "Flag that prepares the runner instance for visionOS testing. Disabled by default." "store_true" -define_arg "os" "17.4" "Sets the expected OS version for installed simulators when --iOS, --tvOS or --visionOS flag is set. Default: '17.4'." "string" "false" +define_arg "watchOS" "false" "Flag that prepares the runner instance for watchOS testing. Disabled by default." "store_true" +define_arg "os" "17.4" "Sets the expected OS version for installed simulators when --iOS, --tvOS, --visionOS or --watchOS flag is set. Default: '17.4'." "string" "false" check_for_help "$@" parse_args "$@" @@ -103,3 +105,14 @@ if [ "$visionOS" = "true" ]; then echo_succ "Found some visionOS Simulator runtime supporting OS '$os'. Skipping..." fi fi + +if [ "$watchOS" = "true" ]; then + echo_subtitle "Supply watchOS Simulator runtime ($os)" + echo "Check current runner for any watchOS Simulator runtime supporting OS '$os':" + if ! xctrace list devices | grep "Apple Watch.*Simulator ($os)"; then + echo_warn "Found no watchOS Simulator runtime supporting OS '$os'. Installing..." + xcodebuild -downloadPlatform watchOS -quiet | xcbeautify + else + echo_succ "Found some watchOS Simulator runtime supporting OS '$os'. Skipping..." + fi +fi From 51e5a5845acf576ddf1ecedeaf33d43ff359995b Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Thu, 4 Jul 2024 16:37:02 +0200 Subject: [PATCH 09/43] Update CHANGELOG.md Add missing changelog entry --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 12b8c5baa9..f407597c5a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ - [IMPROVEMENT] Record Activity Indicator in Session Replay. See [#1934][] - [IMPROVEMENT] Allow disabling app hang monitoring in ObjC API. See [#1908][] - [IMPROVEMENT] Update RUM and Telemetry models with KMP source. See [#1925][] +- [IMPROVEMENT] Use otel-swift fork that only has APIs. See [#1930][] # 2.11.1 / 01-07-2024 @@ -706,6 +707,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1916]: https://github.com/DataDog/dd-sdk-ios/pull/1916 [#1917]: https://github.com/DataDog/dd-sdk-ios/pull/1917 [#1925]: https://github.com/DataDog/dd-sdk-ios/pull/1925 +[#1930]: https://github.com/DataDog/dd-sdk-ios/pull/1930 [#1934]: https://github.com/DataDog/dd-sdk-ios/pull/1934 [#1938]: https://github.com/DataDog/dd-sdk-ios/pull/1938 [@00fa9a]: https://github.com/00FA9A From f0cd12e0bf0d7c6b70deec309a3a8b443bbc7342 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 8 Jul 2024 10:32:23 +0200 Subject: [PATCH 10/43] Add smoke spm build for `DatadogTrace` --- Makefile | 1 + 1 file changed, 1 insertion(+) diff --git a/Makefile b/Makefile index 11c5aade9e..be2f023d62 100644 --- a/Makefile +++ b/Makefile @@ -235,6 +235,7 @@ spm-build-watchos: # Build only compatible schemes for watchOS: @$(MAKE) spm-build DESTINATION="generic/platform=watchOS" SCHEME="DatadogCore" @$(MAKE) spm-build DESTINATION="generic/platform=watchOS" SCHEME="DatadogLogs" + @$(MAKE) spm-build DESTINATION="generic/platform=watchOS" SCHEME="DatadogTrace" # Builds SPM package for macOS (and Mac Catalyst) spm-build-macos: From fef24e04a63c2b415e2778a3565ee42316287d76 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 8 Jul 2024 10:32:38 +0200 Subject: [PATCH 11/43] Update CHANGELOG.md --- CHANGELOG.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index f407597c5a..8ac50a55fe 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +- [FEATURE] Enable DatadogCore, DatadogLogs and DatadogTrace to compile on watchOS platform. See [#1918][] (Thanks [@jfiser-paylocity][]) [#1946][] + # 2.14.0 / 04-07-2024 - [IMPROVEMENT] Use `#fileID` over `#filePath` as the default argument in errors. See [#1938][] @@ -708,6 +710,8 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1917]: https://github.com/DataDog/dd-sdk-ios/pull/1917 [#1925]: https://github.com/DataDog/dd-sdk-ios/pull/1925 [#1930]: https://github.com/DataDog/dd-sdk-ios/pull/1930 +[#1918]: https://github.com/DataDog/dd-sdk-ios/pull/1918 +[#1946]: https://github.com/DataDog/dd-sdk-ios/pull/1946 [#1934]: https://github.com/DataDog/dd-sdk-ios/pull/1934 [#1938]: https://github.com/DataDog/dd-sdk-ios/pull/1938 [@00fa9a]: https://github.com/00FA9A @@ -740,3 +744,4 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [@cltnschlosser]: https://github.com/cltnschlosser [@alexfanatics]: https://github.com/alexfanatics [@changm4n]: https://github.com/changm4n +[@jfiser-paylocity]: https://github.com/jfiser-paylocity From 0595d7dc579a220a79381583c4e64299b14be6fd Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 4 Jul 2024 22:24:30 +0200 Subject: [PATCH 12/43] RUM-4079 Migrate release pipeline to GitLab - prefer shell scripts over python automation - deintegrate Bitrise config - cleanup release.py --- .gitignore | 6 +- .gitlab-ci.yml | 175 ++++++-- Makefile | 59 +++ SmokeTests/carthage/Makefile | 8 +- SmokeTests/cocoapods/Makefile | 4 +- SmokeTests/spm/Makefile | 4 +- SmokeTests/xcframeworks/Makefile | 20 +- bitrise.yml | 58 --- tools/clean.sh | 2 +- tools/distribution/build-xcframework.sh | 108 ----- tools/distribution/release.py | 151 ------- .../src/release/assets/gh_asset.py | 392 ------------------ .../src/release/assets/podspec.py | 107 ----- .../src/release/directory_matcher.py | 42 -- tools/distribution/src/release/git.py | 27 -- tools/distribution/tests/release/__init__.py | 0 .../tests/release/test_directory_matcher.py | 93 ----- tools/env-check.sh | 30 +- tools/release/build-xcframeworks.sh | 151 +++++++ tools/release/build.sh | 62 +++ tools/release/publish-github.sh | 62 +++ tools/release/publish-podspec.sh | 217 ++++++++++ tools/release/validate-version.sh | 52 +++ tools/release/validate-xcframeworks.sh | 152 +++++++ tools/repo-setup/repo-setup.sh | 2 +- tools/runner-setup.sh | 29 +- tools/secrets/check-secrets.sh | 20 + tools/secrets/config.sh | 24 ++ tools/secrets/get-secret.sh | 35 ++ tools/secrets/set-secret.sh | 99 +++++ tools/smoke-test.sh | 4 +- tools/spm-build.sh | 2 +- tools/tools-test.sh | 2 +- tools/ui-test.sh | 2 +- tools/utils/common.mk | 19 +- .../utils/{current_git.sh => current-git.sh} | 2 +- tools/utils/{echo_color.sh => echo-color.sh} | 12 +- 37 files changed, 1184 insertions(+), 1050 deletions(-) delete mode 100755 tools/distribution/build-xcframework.sh delete mode 100755 tools/distribution/release.py delete mode 100644 tools/distribution/src/release/assets/gh_asset.py delete mode 100644 tools/distribution/src/release/assets/podspec.py delete mode 100644 tools/distribution/src/release/directory_matcher.py delete mode 100644 tools/distribution/src/release/git.py delete mode 100644 tools/distribution/tests/release/__init__.py delete mode 100644 tools/distribution/tests/release/test_directory_matcher.py create mode 100755 tools/release/build-xcframeworks.sh create mode 100755 tools/release/build.sh create mode 100755 tools/release/publish-github.sh create mode 100755 tools/release/publish-podspec.sh create mode 100755 tools/release/validate-version.sh create mode 100755 tools/release/validate-xcframeworks.sh create mode 100755 tools/secrets/check-secrets.sh create mode 100644 tools/secrets/config.sh create mode 100755 tools/secrets/get-secret.sh create mode 100755 tools/secrets/set-secret.sh rename tools/utils/{current_git.sh => current-git.sh} (96%) rename tools/utils/{echo_color.sh => echo-color.sh} (85%) diff --git a/.gitignore b/.gitignore index 8f354915c8..f2269ab32d 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,6 @@ Carthage/Build Carthage/Checkouts xcuserdata/ -instrumented-tests/DatadogSDKTesting.* -instrumented-tests/LICENSE *.local.xcconfig @@ -20,3 +18,7 @@ __pycache__ *.swp .venv .vscode +*.pytest_cache + +# CI job artifacts +artifacts/ \ No newline at end of file diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4f066fa369..77be40c64e 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,10 +4,19 @@ stages: - test - ui-test - smoke-test + - release-build + - release-publish variables: MAIN_BRANCH: "master" DEVELOP_BRANCH: "develop" + # Prefilled variables for running a pipeline manually: + # Ref.: https://docs.gitlab.com/ee/ci/pipelines/index.html#prefill-variables-in-manual-pipelines + RELEASE_GIT_TAG: + description: "The Git tag for the release pipeline. If set, release pipeline will be triggered for the given tag." + RELEASE_DRY_RUN: + value: "1" + description: "Controls the dry run mode for the release pipeline. If set to '1', the pipeline will execute all steps but will not publish artifacts. If set to '0', the pipeline will run fully." default: tags: @@ -18,35 +27,39 @@ default: # │ Utility jobs: │ # └───────────────┘ -# Trigger jobs on 'develop' and 'master' branches -.run:when-develop-or-master: - rules: - - if: '$CI_COMMIT_BRANCH == $DEVELOP_BRANCH || $CI_COMMIT_BRANCH == $MAIN_BRANCH' - when: always +# Utility jobs define rules for including or excluding dependent jobs from the pipeline. +# +# Ref.: https://docs.gitlab.com/ee/ci/jobs/job_rules.html +# > Rules are evaluated in order until the first match. When a match is found, the job is either included or excluded +# > from the pipeline, depending on the configuration. -# Trigger jobs on SDK code changes, comparing against 'develop' branch -.run:if-sdk-modified: +.test-pipeline-job: rules: - - changes: + - if: '$CI_COMMIT_BRANCH == $DEVELOP_BRANCH || $CI_COMMIT_BRANCH == $MAIN_BRANCH' # always on main branches + - if: '$CI_COMMIT_BRANCH' # when on other branch with following changes compared to develop + changes: paths: - "Datadog*/**/*" - "IntegrationTests/**/*" - "TestUtilities/**/*" - "*" # match any file in the root directory - compare_to: 'develop' # cannot use variable due to: https://gitlab.com/gitlab-org/gitlab/-/issues/369916 + compare_to: 'develop' # cannot use $DEVELOP_BRANCH var due to: https://gitlab.com/gitlab-org/gitlab/-/issues/369916 -# Trigger jobs on changes in `tools/*`, comparing against 'develop' branch -.run:if-tools-modified: - rules: - - changes: - paths: - - "tools/**/*" - - "Makefile" - - ".gitlab-ci.yml" - compare_to: 'develop' +.release-pipeline-job: + rules: + - if: '$CI_COMMIT_TAG || $RELEASE_GIT_TAG' + +.release-pipeline-delayed-job: + rules: + - if: '$CI_COMMIT_TAG || $RELEASE_GIT_TAG' + when: delayed + start_in: 20 minutes ENV check: stage: pre + rules: + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] script: - make env-check @@ -57,8 +70,7 @@ ENV check: Lint: stage: lint rules: - - !reference [.run:when-develop-or-master, rules] - - !reference [.run:if-sdk-modified, rules] + - !reference [.test-pipeline-job, rules] script: - make clean repo-setup ENV=ci - make lint license-check @@ -67,8 +79,8 @@ Lint: Unit Tests (iOS): stage: test rules: - - !reference [.run:when-develop-or-master, rules] - - !reference [.run:if-sdk-modified, rules] + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] variables: XCODE: "15.3.0" OS: "17.4" @@ -82,8 +94,8 @@ Unit Tests (iOS): Unit Tests (tvOS): stage: test rules: - - !reference [.run:when-develop-or-master, rules] - - !reference [.run:if-sdk-modified, rules] + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] variables: XCODE: "15.3.0" OS: "17.4" @@ -97,8 +109,8 @@ Unit Tests (tvOS): UI Tests: stage: ui-test rules: - - !reference [.run:when-develop-or-master, rules] - - !reference [.run:if-sdk-modified, rules] + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] variables: XCODE: "15.3.0" OS: "17.4" @@ -118,15 +130,22 @@ UI Tests: Tools Tests: stage: test - rules: - - !reference [.run:when-develop-or-master, rules] - - !reference [.run:if-tools-modified, rules] + rules: + - changes: + paths: + - "tools/**/*" + - "Makefile" + - ".gitlab-ci.yml" + compare_to: 'develop' script: - make clean repo-setup ENV=ci - make tools-test Smoke Tests (iOS): stage: smoke-test + rules: + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] tags: - macos:ventura - specific:true @@ -136,13 +155,16 @@ Smoke Tests (iOS): PLATFORM: "iOS Simulator" DEVICE: "iPhone 15 Pro" script: - - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --tvOS --os "$OS" # temporary, waiting for AMI + - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --tvOS --os "$OS" --ssh # temporary, waiting for AMI - make clean repo-setup ENV=ci - make spm-build-ios - make smoke-test-ios-all OS="$OS" PLATFORM="$PLATFORM" DEVICE="$DEVICE" Smoke Tests (tvOS): stage: smoke-test + rules: + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] tags: - macos:ventura - specific:true @@ -152,13 +174,16 @@ Smoke Tests (tvOS): PLATFORM: "tvOS Simulator" DEVICE: "Apple TV" script: - - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --tvOS --os "$OS" # temporary, waiting for AMI + - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --tvOS --os "$OS" --ssh # temporary, waiting for AMI - make clean repo-setup ENV=ci - make spm-build-tvos - make smoke-test-tvos-all OS="$OS" PLATFORM="$PLATFORM" DEVICE="$DEVICE" Smoke Tests (visionOS): stage: smoke-test + rules: + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] tags: - macos:ventura - specific:true @@ -172,6 +197,9 @@ Smoke Tests (visionOS): Smoke Tests (macOS): stage: smoke-test + rules: + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] tags: - macos:ventura - specific:true @@ -181,3 +209,88 @@ Smoke Tests (macOS): - ./tools/runner-setup.sh --xcode "$XCODE" # temporary, waiting for AMI - make clean repo-setup ENV=ci - make spm-build-macos + +# ┌──────────────┐ +# │ SDK release: │ +# └──────────────┘ + +.release-before-script: &export_MAKE_release_params + - export GIT_TAG=${RELEASE_GIT_TAG:-$CI_COMMIT_TAG} # CI_COMMIT_TAG if set, otherwise default to RELEASE_GIT_TAG + - if [ -z "$GIT_TAG" ]; then echo "GIT_TAG is not set"; exit 1; fi # sanity check + - export ARTIFACTS_PATH="artifacts/$GIT_TAG" + - export DRY_RUN=${CI_COMMIT_TAG:+0} # 0 if CI_COMMIT_TAG is set + - export DRY_RUN=${DRY_RUN:-$RELEASE_DRY_RUN} # otherwise default to RELEASE_DRY_RUN + +Build Artifacts: + stage: release-build + rules: + - !reference [.release-pipeline-job, rules] + variables: + XCODE: "15.3.0" + artifacts: + paths: + - artifacts + expire_in: 3 days # TODO: RUM-4079 extend to 6 weeks + before_script: + - *export_MAKE_release_params + script: + - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --tvOS --os "17.4" --ssh # temporary, waiting for AMI + - make env-check + - make clean + - make release-build release-validate + +Publish GH Asset: + stage: release-publish + rules: + - !reference [.release-pipeline-job, rules] + variables: + XCODE: "15.3.0" + before_script: + - *export_MAKE_release_params + script: + - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --tvOS --os "17.4" # temporary, waiting for AMI + - make env-check + - make clean + - make release-publish-github + +Publish CP podspecs (internal): + stage: release-publish + rules: + - !reference [.release-pipeline-job, rules] + variables: + XCODE: "15.3.0" + before_script: + - *export_MAKE_release_params + script: + - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --tvOS --os "17.4" # temporary, waiting for AMI + - make env-check + - make clean + - make release-publish-internal-podspecs + +Publish CP podspecs (dependent): + stage: release-publish + rules: + - !reference [.release-pipeline-delayed-job, rules] + variables: + XCODE: "15.3.0" + before_script: + - *export_MAKE_release_params + script: + - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --tvOS --os "17.4" # temporary, waiting for AMI + - make env-check + - make clean + - make release-publish-dependent-podspecs + +Publish CP podspecs (legacy): + stage: release-publish + rules: + - !reference [.release-pipeline-delayed-job, rules] + variables: + XCODE: "15.3.0" + before_script: + - *export_MAKE_release_params + script: + - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --tvOS --os "17.4" # temporary, waiting for AMI + - make env-check + - make clean + - make release-publish-legacy-podspecs diff --git a/Makefile b/Makefile index c63c0b4561..507963c2b4 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,9 @@ all: env-check repo-setup templates smoke-test smoke-test-ios smoke-test-ios-all smoke-test-tvos smoke-test-tvos-all \ spm-build spm-build-ios spm-build-tvos spm-build-visionos spm-build-macos \ models-generate rum-models-generate sr-models-generate models-verify rum-models-verify sr-models-verify \ + release-build release-validate release-publish-github \ + release-publish-podspec release-publish-internal-podspecs release-publish-dependent-podspecs release-publish-legacy-podspecs \ + set-ci-secret REPO_ROOT := $(PWD) include tools/utils/common.mk @@ -335,6 +338,62 @@ e2e-monitors-generate: @./tools/nightly-e2e-tests/nightly_e2e.py generate-tf --tests-dir ../../Datadog/E2ETests @echo "⚠️ Remember to delete all iOS monitors manually from Mobile-Integration org before running 'terraform apply'." +release-build: + @$(call require_param,GIT_TAG) + @$(call require_param,ARTIFACTS_PATH) + @$(ECHO_TITLE) "make release-build GIT_TAG='$(GIT_TAG)' ARTIFACTS_PATH='$(ARTIFACTS_PATH)'" + ./tools/release/build.sh --tag "$(GIT_TAG)" --artifacts-path "$(ARTIFACTS_PATH)" + +release-validate: + @$(call require_param,GIT_TAG) + @$(call require_param,ARTIFACTS_PATH) + @$(ECHO_TITLE) "make release-validate GIT_TAG='$(GIT_TAG)' ARTIFACTS_PATH='$(ARTIFACTS_PATH)'" + ./tools/release/validate-version.sh --artifacts-path "$(ARTIFACTS_PATH)" --tag "$(GIT_TAG)" + ./tools/release/validate-xcframeworks.sh --artifacts-path "$(ARTIFACTS_PATH)" + +release-publish-github: + @$(call require_param,GIT_TAG) + @$(call require_param,ARTIFACTS_PATH) + @:$(eval DRY_RUN ?= 1) + @:$(eval OVERWRITE_EXISTING ?= 0) + @$(ECHO_TITLE) "make release-publish-github GIT_TAG='$(GIT_TAG)' ARTIFACTS_PATH='$(ARTIFACTS_PATH)' DRY_RUN='$(DRY_RUN)' OVERWRITE_EXISTING='$(OVERWRITE_EXISTING)'" + DRY_RUN=$(DRY_RUN) OVERWRITE_EXISTING=$(OVERWRITE_EXISTING) ./tools/release/publish-github.sh \ + --artifacts-path "$(ARTIFACTS_PATH)" \ + --tag "$(GIT_TAG)" + +release-publish-podspec: + @$(call require_param,PODSPEC_NAME) + @$(call require_param,ARTIFACTS_PATH) + @:$(eval DRY_RUN ?= 1) + @$(ECHO_TITLE) "make release-publish-podspec PODSPEC_NAME='$(PODSPEC_NAME)' ARTIFACTS_PATH='$(ARTIFACTS_PATH)' DRY_RUN='$(DRY_RUN)'" + DRY_RUN=$(DRY_RUN) ./tools/release/publish-podspec.sh \ + --artifacts-path "$(ARTIFACTS_PATH)" \ + --podspec-name "$(PODSPEC_NAME)" + +release-publish-internal-podspecs: + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogInternal.podspec" + +release-publish-dependent-podspecs: + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogCore.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogLogs.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogTrace.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogRUM.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogSessionReplay.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogCrashReporting.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogWebViewTracking.podspec" + +release-publish-legacy-podspecs: + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogObjc.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogAlamofireExtension.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogSDK.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogSDKObjc.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogSDKCrashReporting.podspec" + @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogSDKAlamofireExtension.podspec" + +set-ci-secret: + @$(ECHO_TITLE) "make set-ci-secret" + @./tools/secrets/set-secret.sh + bump: @read -p "Enter version number: " version; \ echo "// GENERATED FILE: Do not edit directly\n\ninternal let __sdkVersion = \"$$version\"" > DatadogCore/Sources/Versioning.swift; \ diff --git a/SmokeTests/carthage/Makefile b/SmokeTests/carthage/Makefile index db10de170e..e55fdb3d2a 100644 --- a/SmokeTests/carthage/Makefile +++ b/SmokeTests/carthage/Makefile @@ -39,13 +39,13 @@ clean: install: @$(ECHO_SUBTITLE2) "make install" - @$(ECHO_SUCCESS) "Preparing for CURRENT_GIT_REF='${CURRENT_GIT_REF}'" + @$(ECHO_INFO) "Preparing for CURRENT_GIT_REF='${CURRENT_GIT_REF}'" sed "s|GIT_REFERENCE|${CURRENT_GIT_REF}|g" Cartfile.src > Cartfile ifeq ($(CARTHAGE_PLATFORM),) @$(ECHO_ERROR) "No CARTHAGE_PLATFORM is defined for PLATFORM='$(PLATFORM)'" @exit 1 endif - @$(ECHO_SUCCESS) "Using CARTHAGE_PLATFORM='$(CARTHAGE_PLATFORM)'" + @$(ECHO_INFO) "Using CARTHAGE_PLATFORM='$(CARTHAGE_PLATFORM)'" carthage update --platform $(CARTHAGE_PLATFORM) --use-xcframeworks test: @@ -61,8 +61,8 @@ ifeq ($(EXPECTED_XCFRAMEWORKS),) @$(ECHO_ERROR) "No EXPECTED_XCFRAMEWORKS is defined for PLATFORM='$(PLATFORM)'" @exit 1 endif - @$(ECHO_SUCCESS) "Using SCHEME='$(SCHEME)'" - @$(ECHO_SUCCESS) "Using EXPECTED_XCFRAMEWORKS='$(EXPECTED_XCFRAMEWORKS)'" + @$(ECHO_INFO) "Using SCHEME='$(SCHEME)'" + @$(ECHO_INFO) "Using EXPECTED_XCFRAMEWORKS='$(EXPECTED_XCFRAMEWORKS)'" @echo "Check XCFrameworks in $(PWD)/Carthage/Build/" @$(foreach xcf,$(EXPECTED_XCFRAMEWORKS),\ if [ -d "Carthage/Build/$(xcf).xcframework" ]; then \ diff --git a/SmokeTests/cocoapods/Makefile b/SmokeTests/cocoapods/Makefile index 084a098656..76a7976104 100644 --- a/SmokeTests/cocoapods/Makefile +++ b/SmokeTests/cocoapods/Makefile @@ -27,12 +27,12 @@ clean: install: @$(ECHO_SUBTITLE2) "make install" - @$(ECHO_SUCCESS) "Preparing for CURRENT_GIT_REF='${CURRENT_GIT_REF}'" + @$(ECHO_INFO) "Preparing for CURRENT_GIT_REF='${CURRENT_GIT_REF}'" ifeq ($(COCOAPODS_GIT_REF),) @$(ECHO_ERROR) "No COCOAPODS_GIT_REF is defined for CURRENT_GIT_REF='$(CURRENT_GIT_REF)'" @exit 1 endif - @$(ECHO_SUCCESS) "Using COCOAPODS_GIT_REF='$(COCOAPODS_GIT_REF)'" + @$(ECHO_INFO) "Using COCOAPODS_GIT_REF='$(COCOAPODS_GIT_REF)'" sed "s|GIT_REFERENCE|${COCOAPODS_GIT_REF}|g" Podfile.src > Podfile bundle exec pod update diff --git a/SmokeTests/spm/Makefile b/SmokeTests/spm/Makefile index 8f7c2b68b9..683a83f12b 100644 --- a/SmokeTests/spm/Makefile +++ b/SmokeTests/spm/Makefile @@ -15,7 +15,7 @@ clean: install: @$(ECHO_SUBTITLE2) "make install" - @$(ECHO_SUCCESS) "Preparing for CURRENT_GIT_REF='${CURRENT_GIT_REF}'" + @$(ECHO_INFO) "Preparing for CURRENT_GIT_REF='${CURRENT_GIT_REF}'" cp -r SPMProject.xcodeproj.src SPMProject.xcodeproj sed "s|GIT_REFERENCE|${CURRENT_GIT_REF}|g" SPMProject.xcodeproj.src/project.pbxproj | \ sed "s|GIT_REMOTE|${CURRENT_GIT_REF}|g" > SPMProject.xcodeproj/project.pbxproj @@ -29,7 +29,7 @@ ifeq ($(SCHEME),) @$(ECHO_ERROR) "No SCHEME is defined for PLATFORM='$(PLATFORM)'" @exit 1 endif - @$(ECHO_SUCCESS) "Using SCHEME='$(SCHEME)'" + @$(ECHO_INFO) "Using SCHEME='$(SCHEME)'" set -eo pipefail; \ xcodebuild -version; \ xcodebuild -project "SPMProject.xcodeproj" -destination "platform=$(PLATFORM),name=$(DEVICE),OS=$(OS)" -scheme "$(SCHEME)" test | xcbeautify diff --git a/SmokeTests/xcframeworks/Makefile b/SmokeTests/xcframeworks/Makefile index 903db38c67..818fc923bc 100644 --- a/SmokeTests/xcframeworks/Makefile +++ b/SmokeTests/xcframeworks/Makefile @@ -5,6 +5,7 @@ include ../../tools/utils/common.mk ifeq ($(PLATFORM), iOS Simulator) SCHEME := App iOS +BUILD_XCFRAMEWORKS_FLAG := --ios EXPECTED_XCFRAMEWORKS := DatadogInternal \ DatadogCore \ DatadogLogs \ @@ -18,6 +19,7 @@ EXPECTED_XCFRAMEWORKS := DatadogInternal \ OpenTelemetryApi else ifeq ($(PLATFORM), tvOS Simulator) SCHEME := App tvOS +BUILD_XCFRAMEWORKS_FLAG := --tvos EXPECTED_XCFRAMEWORKS := DatadogInternal \ DatadogCore \ DatadogLogs \ @@ -34,10 +36,18 @@ clean: rm -rf dd-sdk-ios install: + @$(call require_param,PLATFORM) @$(ECHO_SUBTITLE2) "make install" - @$(ECHO_SUCCESS) "Preparing for CURRENT_GIT_REF='${CURRENT_GIT_REF}'" - git clone --depth 1 --branch ${CURRENT_GIT_REF} https://github.com/DataDog/dd-sdk-ios.git - cd dd-sdk-ios && tools/distribution/build-xcframework.sh + @$(ECHO_INFO) "Preparing for CURRENT_GIT_REF='${CURRENT_GIT_REF}'" + git clone --depth 1 --branch ${CURRENT_GIT_REF} --single-branch git@github.com:DataDog/dd-sdk-ios.git +ifeq ($(BUILD_XCFRAMEWORKS_FLAG),) + @$(ECHO_ERROR) "No BUILD_XCFRAMEWORKS_FLAG is defined for PLATFORM='$(PLATFORM)'" + @exit 1 +endif + cd $(REPO_ROOT) && tools/release/build-xcframeworks.sh \ + --repo-path SmokeTests/xcframeworks/dd-sdk-ios \ + --output-path SmokeTests/xcframeworks/dd-sdk-ios/build/xcframeworks \ + $(BUILD_XCFRAMEWORKS_FLAG) test: @$(call require_param,OS) @@ -52,8 +62,8 @@ ifeq ($(EXPECTED_XCFRAMEWORKS),) @$(ECHO_ERROR) "No EXPECTED_XCFRAMEWORKS is defined for PLATFORM='$(PLATFORM)'" @exit 1 endif - @$(ECHO_SUCCESS) "Using SCHEME='$(SCHEME)'" - @$(ECHO_SUCCESS) "Using EXPECTED_XCFRAMEWORKS='$(EXPECTED_XCFRAMEWORKS)'" + @$(ECHO_INFO) "Using SCHEME='$(SCHEME)'" + @$(ECHO_INFO) "Using EXPECTED_XCFRAMEWORKS='$(EXPECTED_XCFRAMEWORKS)'" @echo "Check XCFrameworks in $(PWD)/dd-sdk-ios/build/xcframeworks/" @$(foreach xcf,$(EXPECTED_XCFRAMEWORKS),\ if [ -d "dd-sdk-ios/build/xcframeworks/$(xcf).xcframework" ]; then \ diff --git a/bitrise.yml b/bitrise.yml index 12def619c1..a8b87a46a9 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -53,7 +53,6 @@ workflows: - _make_dependencies - run_conditioned_workflows - _deploy_artifacts - - start_async_release_jobs - _notify_failure_on_slack _make_dependencies: @@ -181,63 +180,6 @@ workflows: - project_path: Datadog.xcworkspace - xcpretty_test_options: --color --report html --output "${BITRISE_DEPLOY_DIR}/E2E-instrumentation-tests.html" - start_async_release_jobs: - description: |- - Spins off two parallel CI jobs for releasing the actual artifacts. - steps: - - build-router-start: - title: Start new CI jobs for running 'publish_github_asset' and 'publish_cocoapods_podspecs'. - inputs: - - access_token: "$BITRISE_PERSONAL_ACCESS_TOKEN" - - workflows: |- - publish_github_asset - publish_cocoapods_podspecs - - environment_key_list: |- - DD_RELEASE_GIT_TAG - DD_RELEASE_DRY_RUN - - publish_github_asset: - before_run: - - _make_dependencies # install tooling - after_run: - - _notify_failure_on_slack - description: |- - Uploads binaries to Github. - steps: - - script: - title: Publish GH Asset. - inputs: - - content: |- - #!/usr/bin/env zsh - set -e - - # Use 'DD_RELEASE_GIT_TAG' ENV if available, otherwise 'BITRISE_GIT_TAG' ENV - GIT_TAG="${DD_RELEASE_GIT_TAG:-$(echo "${BITRISE_GIT_TAG}")}" - - cd tools/distribution && make clean install - venv/bin/python3 release.py "$GIT_TAG" --only-github - - publish_cocoapods_podspecs: - before_run: - - _make_dependencies # install tooling - after_run: - - _notify_failure_on_slack - description: |- - Submits all podspecs to Cocoapods. - steps: - - script: - title: Submit podspecs to Cocoapods. - inputs: - - content: |- - #!/usr/bin/env zsh - set -e - - # Use 'DD_RELEASE_GIT_TAG' ENV if available, otherwise 'BITRISE_GIT_TAG' ENV - GIT_TAG="${DD_RELEASE_GIT_TAG:-$(echo "${BITRISE_GIT_TAG}")}" - - cd tools/distribution && make clean install - venv/bin/python3 release.py "$GIT_TAG" --only-cocoapods - run_e2e_s8s_upload: description: |- Upload E2E application to Synthetics. diff --git a/tools/clean.sh b/tools/clean.sh index 0bcba2b407..54d27188f7 100755 --- a/tools/clean.sh +++ b/tools/clean.sh @@ -4,7 +4,7 @@ # $ ./tools/clean.sh set -e -source ./tools/utils/echo_color.sh +source ./tools/utils/echo-color.sh clean_dir() { local dir="$1" diff --git a/tools/distribution/build-xcframework.sh b/tools/distribution/build-xcframework.sh deleted file mode 100755 index 1a588c6eb4..0000000000 --- a/tools/distribution/build-xcframework.sh +++ /dev/null @@ -1,108 +0,0 @@ -#!/bin/bash - -function usage() { - cat << EOF -Usage: $(basename "${BASH_SOURCE[0]}") [-h|--help] [-p|--platform] iOS,tvOS [-o|--output] path/to/bundles - -Build and bundle the Datdog SDK xcframeworks. - -Available options: - --h, --help Print this help and exit. --p, --platform Select platform slices to include in the xcframework bundle. Use comma seperated list, default to 'iOS,tvOS'. --o, --output Destination path of the bundles. - -EOF - exit -} - -# default arguments -OUTPUT="build" -PLATFORM="iOS,tvOS" - -# read cmd arguments -while :; do - case $1 in - -p|--platform) PLATFORM=$2 - shift - ;; - -o|--output) OUTPUT_FILE=$2 - shift - ;; - -h|--help) usage - shift - ;; - *) break - esac - shift -done - -ARCHIVE_OUTPUT="$OUTPUT/archives" -XCFRAMEWORK_OUTPUT="$OUTPUT/xcframeworks" - -function archive { - echo "▸ Starts archiving the scheme: $1 for destination: $2;\n▸ Archive path: $3.xcarchive" - xcodebuild archive \ - -workspace Datadog.xcworkspace \ - -scheme "$1" \ - -destination "$2" \ - -archivePath "$3" \ - SKIP_INSTALL=NO \ - BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ - ONLY_ACTIVE_ARCH=NO \ - | xcpretty -} - -function bundle { - PRODUCT=$1 - xcoptions=() - - if [[ $PLATFORM == *"iOS"* ]]; then - echo "▸ Archive $PRODUCT iOS" - - archive "$PRODUCT iOS" "generic/platform=iOS" "$ARCHIVE_OUTPUT/$PRODUCT/ios" - xcoptions+=(-archive "$ARCHIVE_OUTPUT/$PRODUCT/ios.xcarchive" -framework "$PRODUCT.framework") - - archive "$PRODUCT iOS" "generic/platform=iOS Simulator" "$ARCHIVE_OUTPUT/$PRODUCT/ios-simulator" - xcoptions+=(-archive "$ARCHIVE_OUTPUT/$PRODUCT/ios-simulator.xcarchive" -framework "$PRODUCT.framework") - fi - - if [[ $PLATFORM == *"tvOS"* ]]; then - echo "▸ Archive $PRODUCT tvOS" - - archive "$PRODUCT tvOS" "generic/platform=tvOS" "$ARCHIVE_OUTPUT/$PRODUCT/tvos" - xcoptions+=(-archive "$ARCHIVE_OUTPUT/$PRODUCT/tvos.xcarchive" -framework "$PRODUCT.framework") - - archive "$PRODUCT tvOS" "generic/platform=tvOS Simulator" "$ARCHIVE_OUTPUT/$PRODUCT/tvos-simulator" - xcoptions+=(-archive "$ARCHIVE_OUTPUT/$PRODUCT/tvos-simulator.xcarchive" -framework "$PRODUCT.framework") - fi - - echo "▸ Create $PRODUCT.xcframework" - - # Datadog class conflicts with module name and Swift emits invalid module interface - # cf. https://github.com/apple/swift/issues/56573 - # - # Therefore, we cannot provide ABI stability and we have to supply '-allow-internal-distribution'. - xcodebuild -create-xcframework -allow-internal-distribution ${xcoptions[@]} -output "$XCFRAMEWORK_OUTPUT/$PRODUCT.xcframework" -} - -rm -rf $OUTPUT -carthage bootstrap --platform $PLATFORM --use-xcframeworks -mkdir -p "$XCFRAMEWORK_OUTPUT" -cp -r "Carthage/Build/CrashReporter.xcframework" "$XCFRAMEWORK_OUTPUT" -cp -r "Carthage/Build/OpenTelemetryApi.xcframework" "$XCFRAMEWORK_OUTPUT" - -bundle DatadogInternal -bundle DatadogCore -bundle DatadogLogs -bundle DatadogTrace -bundle DatadogRUM -bundle DatadogObjc -bundle DatadogCrashReporting - -# Build iOS-only XCFrameworks -if [[ $PLATFORM == *"iOS"* ]]; then - PLATFORM="iOS" - bundle DatadogWebViewTracking - bundle DatadogSessionReplay -fi \ No newline at end of file diff --git a/tools/distribution/release.py b/tools/distribution/release.py deleted file mode 100755 index c149be91ed..0000000000 --- a/tools/distribution/release.py +++ /dev/null @@ -1,151 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import argparse -import sys -import os -import re -import traceback -from tempfile import TemporaryDirectory -from packaging.version import Version -from src.release.git import clone_repo -from src.release.assets.gh_asset import GHAsset -from src.release.assets.podspec import CPPodspec -import shutil - -DD_SDK_IOS_REPO_SSH = 'git@github.com:DataDog/dd-sdk-ios.git' -DD_SDK_IOS_REPO_NAME = 'dd-sdk-ios' - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument("git_tag", help="Git tag name.") - parser.add_argument( - "--only-github", - action='store_true', - help="Only publish GH Release asset.", - default=os.environ.get('DD_RELEASE_ONLY_GITHUB') == '1' - ) - parser.add_argument( - "--only-cocoapods", - action='store_true', - help="Only publish Cocoapods podspecs.", - default=os.environ.get('DD_RELEASE_ONLY_COCOAPODS') == '1' - ) - parser.add_argument( - "--overwrite-github", - action='store_true', - help="Overwrite existing GH Release asset.", - default=os.environ.get('DD_RELEASE_OVERWRITE_GITHUB') == '1' - ) - parser.add_argument( - "--dry-run", - action='store_true', - help="Run validation but skip publishing.", - default=os.environ.get('DD_RELEASE_DRY_RUN') == '1' - ) - args = parser.parse_args() - - try: - git_tag = args.git_tag - only_github = True if args.only_github else False - only_cocoapods = True if args.only_cocoapods else False - overwrite_github = True if args.overwrite_github else False - dry_run = True if args.dry_run else False - - # Validate arguments: - if only_github and only_cocoapods: - raise Exception('`--only-github` and `--only-cocoapods` cannot be used together.') - - if only_cocoapods and overwrite_github: - raise Exception('`--overwrite-github` and `--only-cocoapods` cannot be used together.') - - version = Version(git_tag) - if not version: - raise Exception(f'Given git tag ("{git_tag}") is invalid, it must comply with Semantic Versioning, see https://semver.org/') - - print(f'🛠️️ ENV:\n' - f'- BITRISE_GIT_TAG = {os.environ.get("BITRISE_GIT_TAG")}\n' - f'- DD_RELEASE_GIT_TAG = {os.environ.get("DD_RELEASE_GIT_TAG")}\n' - f'- DD_RELEASE_ONLY_GITHUB = {os.environ.get("DD_RELEASE_ONLY_GITHUB")}\n' - f'- DD_RELEASE_ONLY_COCOAPODS = {os.environ.get("DD_RELEASE_ONLY_COCOAPODS")}\n' - f'- DD_RELEASE_OVERWRITE_GITHUB = {os.environ.get("DD_RELEASE_OVERWRITE_GITHUB")}\n' - f'- DD_RELEASE_DRY_RUN = {os.environ.get("DD_RELEASE_DRY_RUN")}') - - print(f'🛠️️ ENV and CLI arguments resolved to:\n' - f'- git_tag = {git_tag}\n' - f'- only_github = {only_github}\n' - f'- only_cocoapods = {only_cocoapods}\n' - f'- overwrite_github = {overwrite_github}\n' - f'- dry_run = {dry_run}.') - - print(f'🛠️ Git tag read to version: {version}') - - publish_to_gh = not only_cocoapods - publish_to_cp = not only_github - build_xcfw_relative_path = "tools/distribution/build-xcframework.sh" - build_xcfw_absolute_path = f"{os.getcwd()}/build-xcframework.sh" - - with TemporaryDirectory() as clone_dir: - print(f'ℹ️️ Changing current directory to: {clone_dir}') - os.chdir(clone_dir) - - # Clone repo: - clone_repo(repo_ssh=DD_SDK_IOS_REPO_SSH, repo_name=DD_SDK_IOS_REPO_NAME, git_tag=git_tag) - - print(f'ℹ️️ Changing current directory to: {clone_dir}/{DD_SDK_IOS_REPO_NAME}') - os.chdir(DD_SDK_IOS_REPO_NAME) - # Copy build-xcframework.sh to cloned repo - shutil.copyfile(build_xcfw_absolute_path, build_xcfw_relative_path) - shutil.copymode(build_xcfw_absolute_path, build_xcfw_relative_path) - - # Publish GH Release asset: - if publish_to_gh: - gh_asset = GHAsset(git_tag=git_tag) - gh_asset.validate() - gh_asset.publish(overwrite_existing=overwrite_github, dry_run=dry_run) - - # Publish CP podspecs: - if publish_to_cp: - podspecs = [ - CPPodspec(name='DatadogInternal'), - CPPodspec(name='DatadogCore'), - CPPodspec(name='DatadogLogs'), - CPPodspec(name='DatadogTrace'), - CPPodspec(name='DatadogRUM'), - CPPodspec(name='DatadogSessionReplay'), - CPPodspec(name='DatadogCrashReporting'), - CPPodspec(name='DatadogWebViewTracking'), - CPPodspec(name='DatadogObjc'), - CPPodspec(name='DatadogAlamofireExtension'), - CPPodspec(name='DatadogSDK'), - CPPodspec(name='DatadogSDKObjc'), - CPPodspec(name='DatadogSDKCrashReporting'), - CPPodspec(name='DatadogSDKAlamofireExtension'), - ] - - for podspec in podspecs: - podspec.validate(git_tag=git_tag) - - print('ℹ️️ Checking `pod trunk me` authentication status:') - os.system('pod trunk me') - - for podspec in podspecs: - podspec.publish(dry_run=dry_run) - - print(f'✅️️ All good.') - - except Exception as error: - print(f'❌ Failed to release: {error}') - print('-' * 60) - traceback.print_exc(file=sys.stdout) - print('-' * 60) - sys.exit(1) - - sys.exit(0) diff --git a/tools/distribution/src/release/assets/gh_asset.py b/tools/distribution/src/release/assets/gh_asset.py deleted file mode 100644 index 0a4971416c..0000000000 --- a/tools/distribution/src/release/assets/gh_asset.py +++ /dev/null @@ -1,392 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import os -import glob -from tempfile import TemporaryDirectory, NamedTemporaryFile -from packaging.version import Version -from src.utils import remember_cwd, shell, read_sdk_version, read_xcode_version -from src.release.directory_matcher import DirectoryMatcher - -min_cr_version = Version('1.7.0') -min_tvos_version = Version('1.10.0') -v2 = Version('2.0.0-beta1') - -class XCFrameworkValidator: - name: str - - def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: - pass - - -class DatadogXCFrameworkValidator(XCFrameworkValidator): - name = 'Datadog.xcframework' - - def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: - if in_version >= v2: - return False # Datadog.xcframework no longer exist in `2.0` - - dir = zip_directory.get(self.name) - - # above 1.12.1: framework includes arm64e slices - min_arm64e_version = Version('1.12.1') - if in_version > min_arm64e_version: - dir.assert_it_has_files([ - 'ios-arm64_arm64e', - 'ios-arm64_arm64e/dSYMs/*.dSYM', - 'ios-arm64_arm64e/**/*.swiftinterface', - ]) - else: - dir.assert_it_has_files([ - 'ios-arm64', - 'ios-arm64/dSYMs/*.dSYM', - 'ios-arm64/**/*.swiftinterface', - ]) - - dir.assert_it_has_files([ - 'ios-arm64_x86_64-simulator', - 'ios-arm64_x86_64-simulator/dSYMs/*.dSYM', - 'ios-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - if in_version < min_tvos_version: - return True # Stop here: tvOS support was introduced in `1.10.0` - - dir.assert_it_has_files([ - 'tvos-arm64', - 'tvos-arm64/dSYMs/*.dSYM', - 'tvos-arm64/**/*.swiftinterface', - - 'tvos-arm64_x86_64-simulator', - 'tvos-arm64_x86_64-simulator/dSYMs/*.dSYM', - 'tvos-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - return True - - -class DatadogObjcXCFrameworkValidator(XCFrameworkValidator): - name = 'DatadogObjc.xcframework' - - def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: - # always expect `DatadogObjc.xcframework` - - dir = zip_directory.get(self.name) - - # above 1.12.1: framework includes arm64e slices - min_arm64e_version = Version('1.12.1') - if in_version > min_arm64e_version: - dir.assert_it_has_files([ - 'ios-arm64_arm64e', - 'ios-arm64_arm64e/dSYMs/*.dSYM', - 'ios-arm64_arm64e/**/*.swiftinterface', - ]) - else: - dir.assert_it_has_files([ - 'ios-arm64', - 'ios-arm64/dSYMs/*.dSYM', - 'ios-arm64/**/*.swiftinterface', - ]) - - dir.assert_it_has_files([ - 'ios-arm64_x86_64-simulator', - 'ios-arm64_x86_64-simulator/dSYMs/*.dSYM', - 'ios-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - if in_version < min_tvos_version: - return True # Stop here: tvOS support was introduced in `1.10.0` - - dir.assert_it_has_files([ - 'tvos-arm64', - 'tvos-arm64/dSYMs/*.dSYM', - 'tvos-arm64/**/*.swiftinterface', - - 'tvos-arm64_x86_64-simulator', - 'tvos-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - return True - - -class DatadogCrashReportingXCFrameworkValidator(XCFrameworkValidator): - name = 'DatadogCrashReporting.xcframework' - - def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: - if in_version < min_cr_version: - return False # Datadog Crash Reporting.xcframework was introduced in `1.7.0` - - dir = zip_directory.get(self.name) - - # above 1.12.1: framework includes arm64e slices - min_arm64e_version = Version('1.12.1') - if in_version > min_arm64e_version: - dir.assert_it_has_files([ - 'ios-arm64_arm64e', - 'ios-arm64_arm64e/dSYMs/*.dSYM', - 'ios-arm64_arm64e/**/*.swiftinterface', - ]) - else: - dir.assert_it_has_files([ - 'ios-arm64', - 'ios-arm64/dSYMs/*.dSYM', - 'ios-arm64/**/*.swiftinterface', - ]) - - dir.assert_it_has_files([ - 'ios-arm64_x86_64-simulator', - 'ios-arm64_x86_64-simulator/dSYMs/*.dSYM', - 'ios-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - if in_version < min_tvos_version: - return True # Stop here: tvOS support was introduced in `1.10.0` - - dir.assert_it_has_files([ - 'tvos-arm64', - 'tvos-arm64/**/*.swiftinterface', - - 'tvos-arm64_x86_64-simulator', - 'tvos-arm64_x86_64-simulator/dSYMs/*.dSYM', - 'tvos-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - return True - - -class CrashReporterXCFrameworkValidator(XCFrameworkValidator): - name = 'CrashReporter.xcframework' - - def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: - if in_version < min_cr_version: - return False # Datadog Crash Reporting.xcframework was introduced in `1.7.0` - - dir = zip_directory.get(self.name) - - min_xc14_version = Version('1.12.1') - if in_version >= min_xc14_version: - # 1.12.1 depends on PLCR 1.11.1 which - # no longer include armv7_armv7s slices - # for Xcode 14 support - dir.assert_it_has_files([ - 'ios-arm64_arm64e', - 'ios-arm64_x86_64-simulator', - ]) - else: - dir.assert_it_has_files([ - 'ios-arm64_arm64e_armv7_armv7s', - 'ios-arm64_i386_x86_64-simulator', - ]) - - if in_version < min_tvos_version: - return True # Stop here: tvOS support was introduced in `1.10.0` - - dir.assert_it_has_files([ - 'tvos-arm64', - 'tvos-arm64_x86_64-simulator', - ]) - - return True - - -class KronosXCFrameworkValidator(XCFrameworkValidator): - name = 'Kronos.xcframework' - - def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: - min_version = Version('1.5.0') # First version that depends on Kronos - max_version = Version('1.9.0') # Version where Kronos dependency was removed - if in_version < min_version or in_version >= max_version: - return False - - zip_directory.get(self.name).assert_it_has_files([ - 'ios-arm64_arm64e', - 'ios-arm64_arm64e/dSYMs/*.dSYM', - 'ios-arm64_arm64e/**/*.swiftinterface', - - 'ios-arm64_i386_x86_64-simulator', - 'ios-arm64_i386_x86_64-simulator/dSYMs/*.dSYM', - 'ios-arm64_i386_x86_64-simulator/**/*.swiftinterface', - ]) - - return True - -class DatadogModuleXCFrameworkValidator(XCFrameworkValidator): - def __init__(self, name, platforms = ["ios", "tvos"]): - self.name = f"{name}.xcframework" - self.platforms = platforms - - def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: - if in_version < v2: - return False # introduced in 2.0 - - directory = zip_directory.get(self.name) - - if "ios" in self.platforms : - directory.assert_it_has_files([ - 'ios-arm64_arm64e', - 'ios-arm64_arm64e/dSYMs/*.dSYM', - 'ios-arm64_arm64e/**/*.swiftinterface', - - 'ios-arm64_x86_64-simulator', - 'ios-arm64_x86_64-simulator/dSYMs/*.dSYM', - 'ios-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - - if "tvos" in self.platforms : - directory.assert_it_has_files([ - 'tvos-arm64', - 'tvos-arm64/dSYMs/*.dSYM', - 'tvos-arm64/**/*.swiftinterface', - - 'tvos-arm64_x86_64-simulator', - 'tvos-arm64_x86_64-simulator/dSYMs/*.dSYM', - 'tvos-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - return True - -class OpenTelemetryXCFrameworkValidator(XCFrameworkValidator): - name = 'OpenTelemetryApi.xcframework' - - def validate(self, zip_directory: DirectoryMatcher, in_version: Version) -> bool: - min_otel_version = Version('2.12.0') - if in_version < min_otel_version: - return False # introduced in 2.12.0 - - dir = zip_directory.get(self.name) - - dir.assert_it_has_files([ - 'ios-arm64_x86_64-simulator', - 'ios-arm64_x86_64-simulator/dSYMs/*.dSYM', - 'ios-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - dir.assert_it_has_files([ - 'tvos-arm64', - 'tvos-arm64/**/*.swiftinterface', - - 'tvos-arm64_x86_64-simulator', - 'tvos-arm64_x86_64-simulator/dSYMs/*.dSYM', - 'tvos-arm64_x86_64-simulator/**/*.swiftinterface', - ]) - - return True - -xcframeworks_validators: list[XCFrameworkValidator] = [ - DatadogXCFrameworkValidator(), - KronosXCFrameworkValidator(), - - # 2.0 - DatadogModuleXCFrameworkValidator("DatadogInternal"), - DatadogModuleXCFrameworkValidator("DatadogCore"), - DatadogModuleXCFrameworkValidator("DatadogLogs"), - DatadogModuleXCFrameworkValidator("DatadogTrace"), - DatadogModuleXCFrameworkValidator("DatadogRUM"), - DatadogModuleXCFrameworkValidator("DatadogSessionReplay", platforms=["ios"]), - DatadogModuleXCFrameworkValidator("DatadogWebViewTracking", platforms=["ios"]), - - DatadogObjcXCFrameworkValidator(), - DatadogCrashReportingXCFrameworkValidator(), - CrashReporterXCFrameworkValidator(), - OpenTelemetryXCFrameworkValidator(), -] - -class GHAsset: - """ - The release asset attached to GH Release tag - a `.zip` archive with XCFrameworks found recursively in SDK repo - It uses Carthage for building the actual `.xcframework` bundles (by recursively searching for their Xcode schemes). - """ - - __git_tag: str # The git tag to build assets for - __path: str # The path to the asset `.zip` archive - - def __init__(self, git_tag: str): - print(f'⌛️️️ Creating the GH release asset from {os.getcwd()}') - - this_version = Version(git_tag) - - with NamedTemporaryFile(mode='w+', prefix='dd-gh-distro-', suffix='.xcconfig') as xcconfig: - os.environ['XCODE_XCCONFIG_FILE'] = xcconfig.name - - platform = 'iOS' if this_version < min_tvos_version else 'iOS,tvOS' - - # Produce XCFrameworks: - shell(f'sh tools/distribution/build-xcframework.sh --platform {platform}') - - # Create `.zip` archive: - zip_archive_name = 'Datadog.xcframework.zip' - - # Prior to v2, module stability was not enabled. Therefore, binaries are compiled for - # specific versions of Swift. - if this_version < v2: - xc_version = read_xcode_version().replace(' ', '-') - zip_archive_name = f'Datadog-{read_sdk_version()}-Xcode-{xc_version}.zip' - zip_archive_name = f'Datadog-{read_sdk_version()}.zip' - - with remember_cwd(): - print(f' → Creating GH asset: {zip_archive_name}') - os.chdir('build/xcframeworks') - shell(f'zip -q --symlinks -r {zip_archive_name} *.xcframework') - - self.__path = f'{os.getcwd()}/build/xcframeworks/{zip_archive_name}' - self.__git_tag = git_tag - print(' → GH asset created') - - def __repr__(self): - return f'[GHAsset: path = {self.__path}]' - - def validate(self): - """ - Checks the `.zip` archive integrity with given `git_tag`. - """ - print(f'🔎️️ Validating {self} against: {self.__git_tag}') - - # Check if `sdk_version` matches the git tag name: - sdk_version = read_sdk_version() - if sdk_version != self.__git_tag: - raise Exception(f'The `sdk_version` ({sdk_version}) does not match git tag ({self.__git_tag})') - print(f' → `sdk_version` ({sdk_version}) matches git tag ({self.__git_tag})') - - # Inspect the content of zip archive: - with TemporaryDirectory() as unzip_dir: - shell(f'unzip -q {self.__path} -d {unzip_dir}') - - print(f' → GH asset (zip) content:') - for file_path in glob.iglob(f'{unzip_dir}/**', recursive=True): - print(f' - {file_path.removeprefix(unzip_dir)}') - - dm = DirectoryMatcher(path=unzip_dir) - this_version = Version(self.__git_tag) - - print(f' → Validating each `XCFramework`:') - validated_count = 0 - for validator in xcframeworks_validators: - if validator.validate(zip_directory=dm, in_version=this_version): - print(f' → {validator.name} - OK') - validated_count += 1 - else: - print(f' → {validator.name} - SKIPPING for {this_version}') - - dm.assert_number_of_files(expected_count=validated_count) # assert there are no other files - - print(f' → the content of `.zip` archive is correct') - - def publish(self, overwrite_existing: bool, dry_run: bool): - """ - Uploads the `.zip` archive to GH Release for given `git_tag`. - """ - print(f'📦️️ Publishing {self} to GH Release tag {self.__git_tag}') - - if overwrite_existing: - shell(f'gh release upload {self.__git_tag} {self.__path} --repo DataDog/dd-sdk-ios --clobber', skip=dry_run) - else: - shell(f'gh release upload {self.__git_tag} {self.__path} --repo DataDog/dd-sdk-ios', skip=dry_run) - - print(f' → succeeded') diff --git a/tools/distribution/src/release/assets/podspec.py b/tools/distribution/src/release/assets/podspec.py deleted file mode 100644 index 4c5499280c..0000000000 --- a/tools/distribution/src/release/assets/podspec.py +++ /dev/null @@ -1,107 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import os -import re -import time -import random -from src.utils import shell, read_sdk_version - - -class CPPodspec: - """ - The Cocoapods podspec pushed to `pod trunk`. - """ - - __name: str # The name of the spec, e.g. `DatadogSDK` - __file_name: str # The name of the spec file, e.g. `DatadogSDK.podspec` - __path: str # The path to the `podspec` file - - def __init__(self, name: str): - file_name = f'{name}.podspec' - print(f'⌛️ Searching for `{file_name}` in {os.getcwd()}') - - file_path = f'{os.getcwd()}/{file_name}' - if not os.path.isfile(file_path): - raise Exception(f'Cannot find `{file_name}` in {os.getcwd()}') - - self.__name = name - self.__file_name = file_name - self.__path = file_path - print(f' → `{file_name}` found') - - def __repr__(self): - return f'[CPPodspec: name = {self.__name}, path = {self.__path}]' - - def validate(self, git_tag: str): - """ - Checks the `.podspec` integrity with given `git_tag`. - """ - print(f'🔎️️ Validating {self} against: {git_tag}') - - # Check if spec `.version` matches the git tag name and `sdk_version`: - sdk_version = read_sdk_version() - pod_version = self.__read_pod_version() - if not (pod_version == git_tag and sdk_version == git_tag): - raise Exception(f'`sdk_version` ({sdk_version}), `pod_version` ({pod_version})' - f' and git tag ({git_tag}) do not match') - print(f' → `sdk_version` ({sdk_version}), `pod_version` ({pod_version})' - f' and git tag ({git_tag}) do match') - - def publish(self, dry_run: bool): - """ - Publishes the `.podspec` to pods trunk. - """ - print(f'📦 Publishing {self} ({self.__read_pod_version()})') - - # Because some of our pods depend on others and due to https://github.com/CocoaPods/CocoaPods/issues/9497 - # we need to retry `pod trunk push` until it succeeds. The maximum number of attempts is 100 (arbitrary), - # but this is also limited by the CI job timeout. - retry_time = 30 # seconds - attempt = 0 - while attempt < 100: - attempt += 1 - try: - if attempt == 1: - shell(f'pod repo update', skip=dry_run) - shell(f'pod spec lint --allow-warnings {self.__file_name}', skip=dry_run) - shell(f'pod trunk push --synchronous --allow-warnings {self.__file_name}', skip=dry_run) - else: - shell(f'pod repo update --silent', skip=dry_run) - shell(f'pod spec lint --silent --allow-warnings {self.__file_name}', skip=dry_run) - shell(f'pod trunk push --allow-warnings {self.__file_name}', skip=dry_run) - - if dry_run and random.choice([True, False]): # to enable testing in `dry_run` mode - retry_time = 1 - raise Exception('Running in dry_run mode, simulating `pod` command failure') - - print(f' → succeeded in {attempt} attempt(s)') - break # break the while loop once all succeed without raising an exception - - except Exception: - print(f' → failed on attempt {attempt} (retrying in {retry_time}s)') - - time.sleep(retry_time) - - def __read_pod_version(self) -> str: - """ - Reads pod version from podspec file. - """ - version_regex = r'^.*\.version.*=.*\"([0-9]+\.[0-9]+\.[0-9]+[\-a-z0-9]*)\"' # e.g. 's.version = "1.7.1-alpha1"' - - versions: [str] = [] - with open(self.__path) as podspec_file: - for line in podspec_file.readlines(): - if match := re.match(version_regex, line): - versions.append(match.groups()[0]) - - if len(versions) != 1: - raise Exception(f'Expected one spec `version` in {podspec_file}, but found {len(versions)}: {versions}') - - return versions[0] diff --git a/tools/distribution/src/release/directory_matcher.py b/tools/distribution/src/release/directory_matcher.py deleted file mode 100644 index 8f7f226cb8..0000000000 --- a/tools/distribution/src/release/directory_matcher.py +++ /dev/null @@ -1,42 +0,0 @@ -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import os -import glob - - -class DirectoryMatcherException(Exception): - pass - - -class DirectoryMatcher: - path: str - - def __init__(self, path: str): - if os.path.exists(path): - self.path = path - else: - raise DirectoryMatcherException(f'Directory does not exist: {path}') - - def assert_number_of_files(self, expected_count: int): - actual_count = len(os.listdir(self.path)) - if expected_count != actual_count: - raise DirectoryMatcherException(f'Expected {expected_count} files in "{self.path}", but ' - f'found {actual_count} instead.') - - def assert_it_has_file(self, file_path: str): - search_path = os.path.join(self.path, file_path) - result = list(glob.iglob(search_path, recursive=True)) - - if not result: - raise DirectoryMatcherException(f'Expected "{self.path}" to include {file_path}, but it is missing.') - - def assert_it_has_files(self, file_paths: [str]): - for file_path in file_paths: - self.assert_it_has_file(file_path) - - def get(self, file: str) -> 'DirectoryMatcher': - return DirectoryMatcher(path=os.path.join(self.path, file)) diff --git a/tools/distribution/src/release/git.py b/tools/distribution/src/release/git.py deleted file mode 100644 index f31d091278..0000000000 --- a/tools/distribution/src/release/git.py +++ /dev/null @@ -1,27 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import os - - -def clone_repo(repo_ssh: str, repo_name: str, git_tag: str): - """ - Clones given repo to current directory using GH CLI. - The GH CLI must be authentication by ENV. - """ - print('ℹ️️ Logging GH CLI authentication status:') - os.system('gh auth status') - - print(f'⚙️ Cloning `{repo_name}` (`gh repo clone {repo_ssh} -- -b {git_tag}`)') - result = os.system(f'gh repo clone {repo_ssh} -- -b {git_tag}') - - if result > 0: - raise Exception(f'Failed to clone `{repo_name}`. Check GH CLI authentication status in above logs.') - else: - print(f' → successfully cloned `{repo_name}` (`{git_tag}`) to: {os.getcwd()}') diff --git a/tools/distribution/tests/release/__init__.py b/tools/distribution/tests/release/__init__.py deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/tools/distribution/tests/release/test_directory_matcher.py b/tools/distribution/tests/release/test_directory_matcher.py deleted file mode 100644 index 1d7ac74e6b..0000000000 --- a/tools/distribution/tests/release/test_directory_matcher.py +++ /dev/null @@ -1,93 +0,0 @@ -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - - -import unittest -import os -from tempfile import TemporaryDirectory -from src.release.directory_matcher import DirectoryMatcher, DirectoryMatcherException - - -class DirectoryMatcherTestCase(unittest.TestCase): - def test_initializing(self): - with TemporaryDirectory() as tmp_dir: - self.assertEqual(DirectoryMatcher(path=tmp_dir).path, tmp_dir) - - with self.assertRaises(DirectoryMatcherException): - _ = DirectoryMatcher(path=f'{tmp_dir}/non-existing-path') - - def test_number_of_files(self): - with TemporaryDirectory() as tmp_dir: - dm = DirectoryMatcher(path=tmp_dir) - dm.assert_number_of_files(expected_count=0) - - os.mkdir(os.path.join(tmp_dir, '1')) - os.mkdir(os.path.join(tmp_dir, '2')) - dm.assert_number_of_files(expected_count=2) - - with self.assertRaises(DirectoryMatcherException): - dm.assert_number_of_files(expected_count=4) - - def test_has_file(self): - with TemporaryDirectory() as tmp_dir: - dm = DirectoryMatcher(path=tmp_dir) - os.makedirs(os.path.join(tmp_dir, '1/1A/1AA.xyz')) - os.makedirs(os.path.join(tmp_dir, '1/1A/1AB.xyz')) - os.makedirs(os.path.join(tmp_dir, '2/2A/2AA/foo.xyz')) - os.makedirs(os.path.join(tmp_dir, '2/2A/2AB/foo.xyz')) - - dm.assert_it_has_file('1') - dm.assert_it_has_file('1/1A/1AA.xyz') - dm.assert_it_has_file('1/1A/1AB.xyz') - dm.assert_it_has_file('**/1AA.xyz') - dm.assert_it_has_file('**/1AB.xyz') - dm.assert_it_has_file('**/*.xyz') - dm.assert_it_has_file('**/2A/**/*.xyz') - dm.assert_it_has_file('2') - - with self.assertRaises(DirectoryMatcherException): - dm.assert_it_has_file('foo') - - with self.assertRaises(DirectoryMatcherException): - dm.assert_it_has_file('1A') - - def test_has_files(self): - with TemporaryDirectory() as tmp_dir: - dm = DirectoryMatcher(path=tmp_dir) - os.makedirs(os.path.join(tmp_dir, '1/1A/1AA.xyz')) - os.makedirs(os.path.join(tmp_dir, '1/1A/1AB.xyz')) - os.makedirs(os.path.join(tmp_dir, '2/2A/2AA/foo.xyz')) - os.makedirs(os.path.join(tmp_dir, '2/2A/2AB/foo.xyz')) - - dm.assert_it_has_files([ - '1', - '1/1A/1AA.xyz', - '1/1A/1AB.xyz', - '**/1AA.xyz', - '**/1AB.xyz', - '**/*.xyz', - '**/2A/**/*.xyz', - '2', - ]) - - with self.assertRaises(DirectoryMatcherException): - dm.assert_it_has_files(file_paths=['foo', 'bar']) - - with self.assertRaises(DirectoryMatcherException): - dm.assert_it_has_files(file_paths=['2', '**/foo']) - - def test_get_submatcher(self): - with TemporaryDirectory() as tmp_dir: - os.makedirs(os.path.join(tmp_dir, '1/1A')) - - dm = DirectoryMatcher(path=tmp_dir) - dm.assert_it_has_file('1') - - dm = dm.get('1') - dm.assert_it_has_file('1A') - - with self.assertRaises(DirectoryMatcherException): - dm.assert_it_has_file('1') diff --git a/tools/env-check.sh b/tools/env-check.sh index fcdf6629f7..664f14240a 100755 --- a/tools/env-check.sh +++ b/tools/env-check.sh @@ -1,19 +1,21 @@ #!/bin/zsh # Usage: -# ./tools/env_check.sh +# $ ./tools/env_check.sh # Prints environment information and checks if required tools are installed. set -e -source ./tools/utils/echo_color.sh +source ./tools/utils/echo-color.sh check_if_installed() { - if ! command -v $1 >/dev/null 2>&1; then - echo_err "Error" "$1 is not installed but it is required for development. Install it and try again." - exit 1 - fi + if ! command -v $1 >/dev/null 2>&1; then + echo_err "Error" "$1 is not installed but it is required for development. Install it and try again." + exit 1 + fi } +echo_subtitle "Check versions of installed tools" + echo_succ "System info:" system_profiler SPSoftwareDataType @@ -47,6 +49,11 @@ echo_succ "gh:" check_if_installed gh gh --version +echo "" +echo_succ "vault:" +check_if_installed vault +vault -v + echo "" echo_succ "bundler:" check_if_installed bundler @@ -70,3 +77,14 @@ if command -v brew >/dev/null 2>&1; then echo_succ "brew:" brew -v fi + +if [ "$CI" = "true" ]; then + # Check if all secrets are available: + ./tools/secrets/check-secrets.sh + + echo "" + echo_succ "CI env:" + echo "▸ CI_COMMIT_TAG = ${CI_COMMIT_TAG:-(not set or empty)}" + echo "▸ CI_COMMIT_BRANCH = ${CI_COMMIT_BRANCH:-(not set or empty)}" + echo "▸ CI_COMMIT_SHA = ${CI_COMMIT_SHA:-(not set or empty)}" +fi diff --git a/tools/release/build-xcframeworks.sh b/tools/release/build-xcframeworks.sh new file mode 100755 index 0000000000..df05c77216 --- /dev/null +++ b/tools/release/build-xcframeworks.sh @@ -0,0 +1,151 @@ +#!/bin/zsh + +# Usage: +# $ ./tools/release/build-xcframeworks.sh -h +# Builds XCFrameworks from the specified repository and exports them to the designated output directory. + +# Options: +# --repo-path: The path to the root of the 'dd-sdk-ios' repository. +# --ios: Includes iOS platform slices in the exported XCFrameworks. +# --tvos: Includes tvOS platform slices in the exported XCFrameworks. +# --output-path: The path to the output directory where XCFrameworks will be stored. + +set -eo pipefail +source ./tools/utils/argparse.sh +source ./tools/utils/echo-color.sh + +set_description "Builds XCFrameworks from the specified repository and exports them to the designated output directory." +define_arg "repo-path" "" "The path to the root of the 'dd-sdk-ios' repository." "string" "true" +define_arg "ios" "false" "Includes iOS platform slices in the exported XCFrameworks." "store_true" +define_arg "tvos" "false" "Includes tvOS platform slices in the exported XCFrameworks." "store_true" +define_arg "output-path" "" "The path to the output directory where XCFrameworks will be stored." "string" "true" + +check_for_help "$@" +parse_args "$@" + +rm -rf "$output_path" +mkdir -p "$output_path" + +REPO_PATH=$(realpath "$repo_path") +XCFRAMEWORKS_OUTPUT=$(realpath "$output_path") +ARCHIVES_TEMP_OUTPUT="$XCFRAMEWORKS_OUTPUT/archives" + +check_repo_clean_state() { + echo_subtitle2 "Check repo at '$REPO_PATH'" + + git diff-index --quiet HEAD -- || { echo_err "Error:" "Repository has uncommitted changes."; exit 1; } + + [ -d "Datadog.xcworkspace" ] && echo_succ "Found 'Datadog.xcworkspace' in '$REPO_PATH'" \ + || { echo_err "Error:" "Could not find 'Datadog.xcworkspace' in '$REPO_PATH'." ; exit 1; } + + [ -f "Cartfile" ] && echo_succ "Found 'Cartfile' in '$REPO_PATH'" \ + || { echo_err "Error:" "Could not find 'Cartfile' in '$REPO_PATH'." ; exit 1; } + + [ -f "Cartfile.resolved" ] && echo_succ "Found 'Cartfile.resolved' in '$REPO_PATH'" \ + || { echo_err "Error:" "Could not find 'Cartfile.resolved' in '$REPO_PATH'." ; exit 1; } + + local config_files=$(find . -name "*.local.xcconfig") + if [[ -n $config_files ]]; then + echo_err "Error:" "The repo at '$REPO_PATH' is not in a clean state." + echo "It has following local config files:" + echo "$config_files" | awk '{print "- " $0}' + exit 1 + else + echo_succ "The repository is in a clean state and no '*.local.xcconfig' files are present." + fi +} + +archive() { + local scheme="$1" + local destination="$2" + local archive_path="$3" + + echo_subtitle2 "➔ Archive scheme: '$scheme' for destination: '$destination'" + + xcodebuild archive \ + -workspace "Datadog.xcworkspace" \ + -scheme $scheme \ + -destination $destination \ + -archivePath $archive_path \ + SKIP_INSTALL=NO \ + BUILD_LIBRARY_FOR_DISTRIBUTION=YES \ + ONLY_ACTIVE_ARCH=NO \ + | xcbeautify + + echo_succ "The archive was created successfully at '$archive_path.xcarchive'" +} + +build_xcframework() { + local product="$1" + local platform="$2" + xcoptions=() + + echo_subtitle2 "Build '$product.xcframework' using platform='$platform'" + + if [[ $platform == *"iOS"* ]]; then + echo "▸ Archive $product iOS" + + archive "$product iOS" "generic/platform=iOS" "$ARCHIVES_TEMP_OUTPUT/$product/ios" + xcoptions+=(-archive "$ARCHIVES_TEMP_OUTPUT/$product/ios.xcarchive" -framework "$product.framework") + + archive "$product iOS" "generic/platform=iOS Simulator" "$ARCHIVES_TEMP_OUTPUT/$product/ios-simulator" + xcoptions+=(-archive "$ARCHIVES_TEMP_OUTPUT/$product/ios-simulator.xcarchive" -framework "$product.framework") + fi + + if [[ $platform == *"tvOS"* ]]; then + echo "▸ Archive $product tvOS" + + archive "$product tvOS" "generic/platform=tvOS" "$ARCHIVES_TEMP_OUTPUT/$product/tvos" + xcoptions+=(-archive "$ARCHIVES_TEMP_OUTPUT/$product/tvos.xcarchive" -framework "$product.framework") + + archive "$product tvOS" "generic/platform=tvOS Simulator" "$ARCHIVES_TEMP_OUTPUT/$product/tvos-simulator" + xcoptions+=(-archive "$ARCHIVES_TEMP_OUTPUT/$product/tvos-simulator.xcarchive" -framework "$product.framework") + fi + + # Datadog class conflicts with module name and Swift emits invalid module interface + # cf. https://github.com/apple/swift/issues/56573 + # + # Therefore, we cannot provide ABI stability and we have to supply '-allow-internal-distribution'. + xcodebuild -create-xcframework -allow-internal-distribution ${xcoptions[@]} -output "$XCFRAMEWORKS_OUTPUT/$product.xcframework" | xcbeautify + + echo_succ "The '$product.xcframework' was created successfully in '$XCFRAMEWORKS_OUTPUT'" +} + +echo_info "cd '$REPO_PATH'" +cd $REPO_PATH + +check_repo_clean_state + +# Select PLATFORMS to build ('iOS' | 'tvOS' | 'iOS,tvOS') +PLATFORMS="" +[[ "$ios" == "true" ]] && PLATFORMS+="iOS" +[[ "$tvos" == "true" ]] && { [ -n "$PLATFORMS" ] && PLATFORMS+=","; PLATFORMS+="tvOS"; } + +echo_info "Building xcframeworks:" +echo_info "- REPO_PATH = '$REPO_PATH'" +echo_info "- ARCHIVES_TEMP_OUTPUT = '$ARCHIVES_TEMP_OUTPUT'" +echo_info "- XCFRAMEWORKS_OUTPUT = '$XCFRAMEWORKS_OUTPUT'" +echo_info "- PLATFORMS = '$PLATFORMS'" + +# Build third-party XCFrameworks +echo_subtitle2 "Run 'carthage bootstrap --platform $PLATFORMS --use-xcframeworks'" +carthage bootstrap --platform $PLATFORMS --use-xcframeworks +cp -r "Carthage/Build/CrashReporter.xcframework" "$XCFRAMEWORKS_OUTPUT" +cp -r "Carthage/Build/OpenTelemetryApi.xcframework" "$XCFRAMEWORKS_OUTPUT" + +# Build Datadog XCFrameworks +build_xcframework DatadogInternal "$PLATFORMS" +build_xcframework DatadogCore "$PLATFORMS" +build_xcframework DatadogLogs "$PLATFORMS" +build_xcframework DatadogTrace "$PLATFORMS" +build_xcframework DatadogRUM "$PLATFORMS" +build_xcframework DatadogObjc "$PLATFORMS" +build_xcframework DatadogCrashReporting "$PLATFORMS" + +# Build iOS-only Datadog XCFrameworks +if [[ "$ios" == "true" ]]; then + build_xcframework DatadogWebViewTracking "iOS" + build_xcframework DatadogSessionReplay "iOS" +fi + +rm -rf "$ARCHIVES_TEMP_OUTPUT" diff --git a/tools/release/build.sh b/tools/release/build.sh new file mode 100755 index 0000000000..4b9ceba72a --- /dev/null +++ b/tools/release/build.sh @@ -0,0 +1,62 @@ +#!/bin/zsh + +# Usage: +# $ ./tools/release/build.sh -h +# Builds artifacts for specified tag. + +# Options: +# --tag: The tag to release. +# --artifacts-path: Path to store artifacts. + +set -eo pipefail +source ./tools/utils/argparse.sh +source ./tools/utils/echo-color.sh + +set_description "Builds artifacts for specified tag." +define_arg "tag" "" "The tag to release." "string" "true" +define_arg "artifacts-path" "" "Path to store artifacts." "string" "true" + +check_for_help "$@" +parse_args "$@" + +ARTIFACTS_PATH="$artifacts_path" +REPO_CLONE_PATH="$ARTIFACTS_PATH/dd-sdk-ios" +XCF_DIR_NAME="Datadog.xcframework" +XCF_ZIP_NAME="Datadog.xcframework.zip" + +# Clone a fresh version of the repo to the artifacts path to ensure that release tools +# operate on a clean version of the repo, unaltered by any configuration changes. +clone_repo () { + echo_subtitle "Clone repo for '$tag' into '$ARTIFACTS_PATH'" + git clone --depth 1 --branch $tag --single-branch git@github.com:DataDog/dd-sdk-ios.git $REPO_CLONE_PATH +} + +# Create XCFrameworks for the repo clone using release tools from the current repo. +create_xcframeworks () { + echo_subtitle "Create '$XCF_DIR_NAME' in '$ARTIFACTS_PATH'" + ./tools/release/build-xcframeworks.sh --repo-path "$REPO_CLONE_PATH" \ + --ios --tvos \ + --output-path "$ARTIFACTS_PATH/$XCF_DIR_NAME" + + echo_info "Contents of '$ARTIFACTS_PATH/$XCF_DIR_NAME':" + ls $ARTIFACTS_PATH/$XCF_DIR_NAME +} + +create_zip_archive () { + echo_subtitle "Create '$XCF_ZIP_NAME' in '$ARTIFACTS_PATH'" + cd "$ARTIFACTS_PATH" && zip -r -q $XCF_ZIP_NAME $XCF_DIR_NAME + cd - +} + +rm -rf "$ARTIFACTS_PATH" +mkdir -p "$ARTIFACTS_PATH" + +clone_repo +create_xcframeworks +create_zip_archive + +# Cleanup: +rm -rf "$ARTIFACTS_PATH/$XCF_DIR_NAME" + +echo_succ "Success. Artifacts ready in '$ARTIFACTS_PATH':" +ls -lh $ARTIFACTS_PATH diff --git a/tools/release/publish-github.sh b/tools/release/publish-github.sh new file mode 100755 index 0000000000..62486f68a9 --- /dev/null +++ b/tools/release/publish-github.sh @@ -0,0 +1,62 @@ +#!/bin/zsh + +# Usage: +# $ ./tools/release/publish-github.sh -h +# Publishes GitHub asset to GH release. + +# Options: +# --tag: The tag to publish GitHub asset to. +# --artifacts-path: Path to build artifacts. +# --overwrite-existing: Overwrite existing GH asset. +# --dry-run: Do everything except publishing the GitHub asset. + +set -eo pipefail +source ./tools/utils/argparse.sh +source ./tools/utils/echo-color.sh +source ./tools/secrets/get-secret.sh + +set_description "Publishes GitHub asset to GH release." +define_arg "tag" "" "The tag to publish GitHub asset to." "string" "true" +define_arg "overwrite-existing" "false" "Overwrite existing GH asset." "store_true" +define_arg "dry-run" "false" "Do everything except publishing the GitHub asset." "store_true" +define_arg "artifacts-path" "" "Path to build artifacts." "string" "true" + +check_for_help "$@" +parse_args "$@" + +GH_ASSET_PATH="$artifacts_path/Datadog.xcframework.zip" +REPO_NAME="DataDog/dd-sdk-ios" + +authenticate() { + echo_subtitle "Authenticate 'gh' CLI" + echo_info "Exporting 'GITHUB_TOKEN' for CI" + export GITHUB_TOKEN=$(get_secret $DD_IOS_SECRET__GH_CLI_TOKEN) + echo_info "▸ gh auth status" + gh auth status + if [[ $? -ne 0 ]]; then + echo_err "Error:" "GitHub CLI is not authenticated." + exit 1 + fi +} + +upload() { + echo_subtitle "Upload GH asset to '$tag' release" + ghoptions=() + if [ "$OVERWRITE_EXISTING" = "1" ] || [ "$OVERWRITE_EXISTING" = "true" ]; then + ghoptions+=(--clobber) + fi + + echo_info "▸ gh release upload $tag '$GH_ASSET_PATH' --repo '$REPO_NAME' ${ghoptions[@]}" + if [ "$DRY_RUN" = "1" ] || [ "$DRY_RUN" = "true" ]; then + echo_warn "Running in DRY RUN mode. Skipping." + else + gh release upload $tag "$GH_ASSET_PATH" --repo "$REPO_NAME" ${ghoptions[@]} + fi +} + +echo_info "Publishing '$GH_ASSET_PATH' to '$tag' release in '$REPO_NAME'" +echo "▸ Using DRY_RUN = $DRY_RUN" +echo "▸ Using OVERWRITE_EXISTING = $OVERWRITE_EXISTING" + +authenticate +upload diff --git a/tools/release/publish-podspec.sh b/tools/release/publish-podspec.sh new file mode 100755 index 0000000000..ed7a371364 --- /dev/null +++ b/tools/release/publish-podspec.sh @@ -0,0 +1,217 @@ +#!/bin/zsh + +# Usage: +# $ ./tools/release/publish-podspec.sh -h +# Publishes podspec to Cocoapods trunk. + +# Options: +# --artifacts-path: The path to build artifacts. +# --podspec-name: The name of podspec file to publish. + +set -eo pipefail +source ./tools/utils/argparse.sh +source ./tools/utils/echo-color.sh +source ./tools/secrets/get-secret.sh + +set_description "Publishes podspec to Cocoapods trunk." +define_arg "podspec-name" "" "The name of podspec file to publish." "string" "true" +define_arg "artifacts-path" "" "The path to build artifacts." "string" "true" + +check_for_help "$@" +parse_args "$@" + +REPO_PATH="$artifacts_path/dd-sdk-ios" +PODSPEC_PATH="$REPO_PATH/$podspec_name" + +authenticate() { + echo_subtitle "Authenticate 'pod trunk' CLI" + echo_info "Exporting 'COCOAPODS_TRUNK_TOKEN' for CI" + export COCOAPODS_TRUNK_TOKEN=$(get_secret $DD_IOS_SECRET__CP_TRUNK_TOKEN) + echo_info "▸ bundle exec pod trunk me" && bundle exec pod trunk me + if [[ $? -ne 0 ]]; then + echo_err "Error: 'pod trunk' is not authenticated." + exit 1 + fi +} + +lint() { + echo_subtitle "Lint '$podspec_name'" + echo_info "▸ cd '$REPO_PATH'" && cd "$REPO_PATH" + echo_info "▸ bundle exec pod repo update --silent" && bundle exec pod repo update --silent + echo_info "▸ bundle exec pod spec lint --allow-warnings '$podspec_name'" + LAST_POD_OUTPUT=$(bundle exec pod spec lint --allow-warnings "$podspec_name" 2>&1) + LAST_POD_EXIT_CODE=$? + echo_info "▸ cd -" && cd - +} + +push() { + echo_subtitle "Push '$podspec_name' to trunk" + echo_info "▸ cd '$REPO_PATH'" && cd "$REPO_PATH" + echo_info "▸ bundle exec pod trunk push --synchronous --allow-warnings '$podspec_name'" + if [ "$DRY_RUN" = "1" ] || [ "$DRY_RUN" = "true" ]; then + echo_warn "Running in DRY RUN mode. Skipping." + LAST_POD_OUTPUT="" + LAST_POD_EXIT_CODE=0 + else + LAST_POD_OUTPUT=$(bundle exec pod trunk push --synchronous --allow-warnings "$podspec_name" 2>&1) + LAST_POD_EXIT_CODE=$? + fi + echo_info "▸ cd -" && cd - +} + +# Track the exit state of the last `pod` command: +LAST_POD_OUTPUT="" +LAST_POD_EXIT_CODE=-1 + +# Status indicating the pod command returned success. +POD_STATUS__SUCCESS=0 + +# Status indicating the pod command needs a retry due to a podspec dependency issue. +# Likely one of the dependencies is still being processed in trunk so we need to wait and retry. +POD_STATUS__NEEDS_RETRY=1 + +# Status indicating the podspec already exists in the trunk. +# Likely we're trying to re-publish a podspec that previously succeeded so we should skip. +POD_STATUS__ALREADY_EXISTS=2 + +# Status indicating an unexpected error occurred when processing the podspec. +POD_STATUS__ERROR=3 + +# Checks the status of the last executed `pod` command. +# +# It validates LAST_POD_EXIT_CODE and parses the LAST_POD_OUTPUT in order to look for known reasons for +# pod command failure. It returns one of the predefined POD_STATUS__ values. +# +# Returns: +# POD_STATUS__SUCCESS : If the command succeeds. +# POD_STATUS__NEEDS_RETRY : If the command fails due to a dependency issue and requires retrying. +# POD_STATUS__ALREADY_EXISTS : If the podspec already exists in the trunk. +# POD_STATUS__ERROR : If an unexpected error occured in the last `pod` command. +check_pod_command_status() { + # This error likely indicates that podspec for one of dependencies isn not yet available + # + # Example: + # ``` + # -> DatadogObjc (2.11.1) + # - ERROR | [iOS] unknown: Encountered an unknown error (CocoaPods could not find compatible versions for pod "DatadogRUM": + # In Podfile: + # DatadogObjc (from `/private/var/.../dd-sdk-ios/DatadogObjc.podspec`) was resolved to 2.11.1, which depends on + # DatadogRUM (= 2.11.1) + # ``` + # Ref.: https://github.com/CocoaPods/Molinillo/blob/1d62d7d5f448e79418716dc779a4909509ccda2a/lib/molinillo/errors.rb#L106 + POD_DEPENDENCY_ERROR="CocoaPods could not find compatible versions for pod" + + # Example: + # ``` + # Validating podspec + # -> DatadogInternal + # -> DatadogInternal (2.11.1) + # - NOTE | ... + # [!] Unable to accept duplicate entry for: DatadogInternal (2.11.1) + # ``` + # Ref.: https://github.com/CocoaPods/trunk.cocoapods.org/blob/b6c897b53dd7a33e5fb2715e08a72d47901fe85f/app/controllers/api/pods_controller.rb#L174 + POD_DUPLICATE_ERROR="Unable to accept duplicate entry for: ${podspec_name%.*}" + + echo "▸ Parsing output for last pod command (exit code: $LAST_POD_EXIT_CODE)" + + if [ "$LAST_POD_EXIT_CODE" = "0" ]; then + echo_succ "▸ returning POD_STATUS__SUCCESS" + return $POD_STATUS__SUCCESS + fi + + if echo "$LAST_POD_OUTPUT" | grep -q "$POD_DEPENDENCY_ERROR"; then + echo_warn "▸ Matched dependency error:" + echo "..." + echo "$LAST_POD_OUTPUT" | grep -C 3 "$POD_DEPENDENCY_ERROR" + echo "..." + echo_warn "▸ Returning POD_STATUS__NEEDS_RETRY" + return $POD_STATUS__NEEDS_RETRY + fi + + if echo "$LAST_POD_OUTPUT" | grep -q "$POD_DUPLICATE_ERROR"; then + echo_warn "▸ Matched duplicated entry error:" + echo "..." + echo "$LAST_POD_OUTPUT" | grep -C 3 "$POD_DUPLICATE_ERROR" + echo "..." + echo_warn "▸ Returning POD_STATUS__ALREADY_EXISTS" + return $POD_STATUS__ALREADY_EXISTS + fi + + echo_err "▸ Encountered unexpected pod output:" + echo "$LAST_POD_OUTPUT" + echo_err "▸ Returning POD_STATUS__ERROR" + return $POD_STATUS__ERROR +} + +# This function attempts to execute a specified pod command, checking its status after each execution. +# If the command fails due to a dependency issue, it retries the command up to a specified number of times +# with a delay between each attempt. +# +# Parameters: +# command (string) : The pod command to be executed. +# max_retries (int) : The maximum number of retry attempts if the command needs to be retried. +# retry_delay (int) : The delay (in seconds) between retry attempts. +# +# Returns: +# POD_STATUS__SUCCESS : If the command succeeds. +# POD_STATUS__NEEDS_RETRY : If the command fails due to a dependency issue and requires retrying. +# POD_STATUS__ALREADY_EXISTS : If the podspec already exists in the trunk. +# POD_STATUS__ERROR : If an unexpected error occurs or if the maximum number of retries is exceeded. +# +retry_pod_command() { + local command=$1 + local max_retries=$2 + local retry_delay=$3 + local retry_count=0 + + while [ $retry_count -lt $max_retries ]; do + $command + check_pod_command_status + local pod_status=$? + if [ $pod_status -eq $POD_STATUS__SUCCESS ]; then + echo_succ "▸ Success" + return $POD_STATUS__SUCCESS + elif [ $pod_status -eq $POD_STATUS__NEEDS_RETRY ]; then + echo_info "▸ Retrying due to dependency issue (attempt $((retry_count + 1))/$max_retries)..." + retry_count=$((retry_count + 1)) + sleep $retry_delay + elif [ $pod_status -eq $POD_STATUS__ALREADY_EXISTS ]; then + echo_succ "▸ Podspec already exists in trunk. Skipping." + return $POD_STATUS__ALREADY_EXISTS + elif [ $pod_status -eq $POD_STATUS__ERROR ]; then + echo_err "▸ Error encountered. Exiting." + return $POD_STATUS__ERROR + fi + done + + echo_err "▸ Exceeded maximum retries. Exiting." + return $POD_STATUS__ERROR +} + +echo_info "Publishing '$podspec_name'" +echo "▸ Using PODSPEC_PATH = $PODSPEC_PATH" +echo "▸ Using DRY_RUN = $DRY_RUN" + +echo_info "▸ cd '$REPO_PATH'" && cd "$REPO_PATH" +echo_info "▸ bundle install --quiet" && bundle install --quiet +echo_info "▸ cd -" && cd - + +authenticate + +set +e # disable exit on error because we do custom error handling here + +# Retry lint command (up to 50 times with 1 minute delay) +retry_pod_command lint 50 60 +lint_status=$? +if [ $lint_status -ne $POD_STATUS__SUCCESS ] && [ $lint_status -ne $POD_STATUS__ALREADY_EXISTS ]; then + exit 1 +fi + +# Retry push command (up to 50 times with 1 minute delay) +retry_pod_command push 50 60 +push_status=$? +if [ $push_status -ne $POD_STATUS__SUCCESS ] && [ $push_status -ne $POD_STATUS__ALREADY_EXISTS ]; then + exit 1 +fi + +set -e diff --git a/tools/release/validate-version.sh b/tools/release/validate-version.sh new file mode 100755 index 0000000000..a8018df295 --- /dev/null +++ b/tools/release/validate-version.sh @@ -0,0 +1,52 @@ +#!/bin/zsh + +# Usage: +# $ ./tools/release/validate-version.sh -h +# Validates SDK and podspec versions against release tag. + +# Options: +# --tag: The tag to validate versions. +# --artifacts-path: The path to build artifacts. + +set -eo pipefail +source ./tools/utils/argparse.sh +source ./tools/utils/echo-color.sh + +set_description "Validates SDK and podspec versions against release tag." +define_arg "tag" "" "The tag to validate versions." "string" "true" +define_arg "artifacts-path" "" "The path to build artifacts." "string" "true" + +check_for_help "$@" +parse_args "$@" + +REPO_PATH="$artifacts_path/dd-sdk-ios" +SDK_VERSION_FILE="$REPO_PATH/DatadogCore/Sources/Versioning.swift" + +check_sdk_version () { + echo_subtitle "Check 'sdk_version'" + sdk_version=$(grep '__sdkVersion' $SDK_VERSION_FILE | awk -F '"' '{print $2}') + if [[ "$sdk_version" == "$tag" ]]; then + echo_succ "▸ SDK version in '$SDK_VERSION_FILE' ('$sdk_version') matches the tag '$tag'" + else + echo_err "▸ Error:" "SDK version in '$SDK_VERSION_FILE' ('$sdk_version') does not match tag '$tag'" + exit 1 + fi +} + +check_podspec_versions () { + echo_subtitle "Check podspec versions in '$REPO_PATH/*.podspec'" + for podspec_file in $(find $REPO_PATH -type f -name "*.podspec" -maxdepth 1); do + spec_name=$(basename "$podspec_file") + spec_version=$(grep -E '^\s*s\.version\s*=' $podspec_file | awk -F '"' '{print $2}') + + if [[ "$spec_version" == "$tag" ]]; then + echo_succ "▸ '$spec_name' version ('$spec_version') matches the tag '$tag'" + else + echo_err "▸ Error:" "'$spec_name' version ('$spec_version') does not match tag '$tag'" + exit 1 + fi + done +} + +check_sdk_version +check_podspec_versions diff --git a/tools/release/validate-xcframeworks.sh b/tools/release/validate-xcframeworks.sh new file mode 100755 index 0000000000..fc472bb06e --- /dev/null +++ b/tools/release/validate-xcframeworks.sh @@ -0,0 +1,152 @@ +#!/bin/zsh + +# Usage: +# $ ./tools/release/validate-xcframeworks.sh -h +# Validates contents of xcframeworks. + +# Options: +# --artifacts-path: The path to build artifacts. + +set -eo pipefail +source ./tools/utils/argparse.sh +source ./tools/utils/echo-color.sh + +set_description "Validates contents of xcframeworks." +define_arg "artifacts-path" "" "The path to build artifacts." "string" "true" + +check_for_help "$@" +parse_args "$@" + +XCF_ZIP_NAME="Datadog.xcframework.zip" +XCF_ZIP_PATH="$artifacts_path/$XCF_ZIP_NAME" + +unzip_archive() { + local zip_path=$1 + local output_dir=$2 + + echo_info "Uncompressing '$zip_path' to '$output_dir'" + unzip -q "$zip_path" -d "$output_dir" +} + +check_xcframework_bundle_exists() { + local xcframework_path=$1 + + if [[ -d "$xcframework_path" ]]; then + echo_succ "▸ '$xcframework_path' found." + else + echo_err "▸ '$xcframework_path' not found." + exit 1 + fi +} + +validate_xcframework() { + local framework_name=$1; shift + local slice=("$@") + + echo_subtitle2 "Validate contents of '$framework_name'" + + local framework_path="$XCF_PATH/$framework_name" + if [[ ! -d "$framework_path" ]]; then + echo_err "▸ '$framework_path' not found." + exit 1 + fi + + echo "Checking required files in '$framework_name':" + for pattern in "${slice[@]}"; do + local found_files=($(find "$framework_path" -path "$framework_path/$pattern")) + if [[ ${#found_files[@]} -eq 0 ]]; then + echo_err "▸ '$pattern' not found." + exit 1 + else + echo_succ "▸ '$pattern' found and removed." + for file in "${found_files[@]}"; do + rm -rf "$file" # remove so we can list unmatched files later + done + fi + done + + echo "Listing remaining files in '$framework_name':" + find "$framework_path" -type f | while read -r file; do + echo_warn "▸ ${file#$framework_path/}" + done + + rm -rf "$framework_path" # remove so we can list remaining xcframeworks later +} + +# Checks if there are any remaining files after validation (during validation, all known files are deleted). +check_remaining_files() { + local xcframework_path=$1 + + echo_subtitle "Check unexpected files in '$XCF_ZIP_NAME'" + remaining_files=$(find "$xcframework_path" -mindepth 1 -maxdepth 1) + if [[ -n "$remaining_files" ]]; then + echo_err "Error:" "Remaining files found but not expected:" + find "$xcframework_path" -mindepth 1 -maxdepth 1 | while read -r file; do + echo "▸ ${file#$xcframework_path/}" + done + exit 1 + else + echo_succ "No extra files found. All good." + fi +} + +# Uncompress the archive to temporary directory +temp_dir=$(mktemp -d) +trap "rm -rf $temp_dir" EXIT INT + +echo_subtitle "Validate xcframeworks in '$XCF_ZIP_NAME'" +unzip_archive "$XCF_ZIP_PATH" "$temp_dir" + +# Check if the main bundle exists in the archive +XCF_PATH="$temp_dir/Datadog.xcframework" +check_xcframework_bundle_exists "$XCF_PATH" + +# Define slices for validation +IOS=( + "ios-arm64_arm64e" + "ios-arm64_x86_64-simulator" +) + +IOS_SWIFT=( + "ios-arm64_arm64e/**/*.swiftinterface" + "ios-arm64_x86_64-simulator/**/*.swiftinterface" +) + +IOS_DSYMs=( + "ios-arm64_arm64e/dSYMs/*.dSYM" + "ios-arm64_x86_64-simulator/dSYMs/*.dSYM" +) + +TVOS=( + "tvos-arm64" + "tvos-arm64_x86_64-simulator" +) + +TVOS_SWIFT=( + "tvos-arm64/**/*.swiftinterface" + "tvos-arm64_x86_64-simulator/**/*.swiftinterface" +) + +TVOS_DSYMs=( + "tvos-arm64/dSYMs/*.dSYM" + "tvos-arm64_x86_64-simulator/dSYMs/*.dSYM" +) + +DATADOG_IOS=("${IOS_SWIFT[@]}" "${IOS_DSYMs[@]}" "${IOS[@]}") +DATADOG_TVOS=("${TVOS_SWIFT[@]}" "${TVOS_DSYMs[@]}" "${TVOS[@]}") + +# Validate xcframeworks from the archive +validate_xcframework "DatadogInternal.xcframework" "${DATADOG_IOS[@]}" "${DATADOG_TVOS[@]}" +validate_xcframework "DatadogCore.xcframework" "${DATADOG_IOS[@]}" "${DATADOG_TVOS[@]}" +validate_xcframework "DatadogLogs.xcframework" "${DATADOG_IOS[@]}" "${DATADOG_TVOS[@]}" +validate_xcframework "DatadogTrace.xcframework" "${DATADOG_IOS[@]}" "${DATADOG_TVOS[@]}" +validate_xcframework "DatadogRUM.xcframework" "${DATADOG_IOS[@]}" "${DATADOG_TVOS[@]}" +validate_xcframework "DatadogObjc.xcframework" "${DATADOG_IOS[@]}" "${DATADOG_TVOS[@]}" +validate_xcframework "DatadogCrashReporting.xcframework" "${DATADOG_IOS[@]}" "${DATADOG_TVOS[@]}" +validate_xcframework "DatadogSessionReplay.xcframework" "${DATADOG_IOS[@]}" +validate_xcframework "DatadogWebViewTracking.xcframework" "${DATADOG_IOS[@]}" +validate_xcframework "OpenTelemetryApi.xcframework" "${DATADOG_IOS[@]}" "${DATADOG_TVOS[@]}" +validate_xcframework "CrashReporter.xcframework" "${IOS[@]}" "${TVOS[@]}" + +# Check if archive has any remaining files +check_remaining_files "$XCF_PATH" diff --git a/tools/repo-setup/repo-setup.sh b/tools/repo-setup/repo-setup.sh index ff15233a2d..4e47aa3ec0 100755 --- a/tools/repo-setup/repo-setup.sh +++ b/tools/repo-setup/repo-setup.sh @@ -9,7 +9,7 @@ set -eo pipefail source ./tools/utils/argparse.sh -source ./tools/utils/echo_color.sh +source ./tools/utils/echo-color.sh set_description "Prepares the repository for development and testing in given ENV." define_arg "env" "" "Specifies the environment for preparation. Use 'dev' for local development and 'ci' for CI." "string" "true" diff --git a/tools/runner-setup.sh b/tools/runner-setup.sh index fd4e391bc0..c60a6d54fa 100755 --- a/tools/runner-setup.sh +++ b/tools/runner-setup.sh @@ -10,10 +10,12 @@ # --tvOS: Flag that prepares the runner instance for tvOS testing. Disabled by default. # --visionOS: Flag that prepares the runner instance for visionOS testing. Disabled by default. # --os: Sets the expected OS version for installed simulators when --iOS, --tvOS or --visionOS flag is set. Default: '17.4'. +# --ssh: Flag that adds ssh configuration for interacting with GitHub repositories. Disabled by default. set -eo pipefail -source ./tools/utils/echo_color.sh +source ./tools/utils/echo-color.sh source ./tools/utils/argparse.sh +source ./tools/secrets/get-secret.sh set_description "This script is for TEMPORARY. It supplements missing components on the runner. It will be removed once all configurations are integrated into the AMI." define_arg "xcode" "" "Sets the Xcode version on the runner." "string" "false" @@ -21,6 +23,7 @@ define_arg "iOS" "false" "Flag that prepares the runner instance for iOS testing define_arg "tvOS" "false" "Flag that prepares the runner instance for tvOS testing. Disabled by default." "store_true" define_arg "visionOS" "false" "Flag that prepares the runner instance for visionOS testing. Disabled by default." "store_true" define_arg "os" "17.4" "Sets the expected OS version for installed simulators when --iOS, --tvOS or --visionOS flag is set. Default: '17.4'." "string" "false" +define_arg "ssh" "false" "Flag that adds ssh configuration for interacting with GitHub repositories. Disabled by default." "store_true" check_for_help "$@" parse_args "$@" @@ -103,3 +106,27 @@ if [ "$visionOS" = "true" ]; then echo_succ "Found some visionOS Simulator runtime supporting OS '$os'. Skipping..." fi fi + +if [ "$ssh" = "true" ]; then + # Adds SSH config, so we can git clone GH repos. + echo_subtitle "Add SSH configuration" + SSH_KEY_PATH="$HOME/.ssh/id_ed25519" + SSH_CONFIG_PATH="$HOME/.ssh/config" + + if [ ! -f "$SSH_KEY_PATH" ] || [ ! -f "$SSH_CONFIG_PATH" ]; then + echo_warn "Found no SSH key or SSH config file. Configuring..." + get_secret $DD_IOS_SECRET__SSH_KEY > $SSH_KEY_PATH + chmod 600 "$SSH_KEY_PATH" + + cat < "$HOME/.ssh/config" +Host github.com + HostName github.com + User git + IdentityFile $SSH_KEY_PATH + StrictHostKeyChecking no +EOF + echo_succ "Finished SSH setup." + else + echo_succ "Found both SSH key and SSH config file. Skipping..." + fi +fi diff --git a/tools/secrets/check-secrets.sh b/tools/secrets/check-secrets.sh new file mode 100755 index 0000000000..76e87b28df --- /dev/null +++ b/tools/secrets/check-secrets.sh @@ -0,0 +1,20 @@ +#!/bin/zsh + +# Checks if all secret values are available in current env. +# +# Usage: +# $ ./tools/secrets/set-secret.sh +# +# Note: +# - Requires `vault` to be installed + +set -eo pipefail +source ./tools/utils/echo-color.sh +source ./tools/secrets/get-secret.sh + +echo_subtitle "Check if secret values are available" + +for key in ${(k)DD_IOS_SECRETS}; do + secret_name=${DD_IOS_SECRETS[$key]%% |*} + get_secret $secret_name > /dev/null && echo_succ "$secret_name - OK" +done diff --git a/tools/secrets/config.sh b/tools/secrets/config.sh new file mode 100644 index 0000000000..527f03cc4d --- /dev/null +++ b/tools/secrets/config.sh @@ -0,0 +1,24 @@ +#!/bin/zsh + +DD_VAULT_ADDR=https://vault.us1.ddbuild.io + +# The common path prefix for all dd-sdk-ios secrets in Vault. +# +# When using `vault kv put` to write secrets to a specific path, Vault overwrites the entire set of secrets +# at that path with the new data. This means that any existing secrets at that path are replaced by the new +# secrets. For simplicity, we store each secret independently by writing each to a unique path. +DD_IOS_SECRETS_PATH_PREFIX='kv/aws/arn:aws:iam::486234852809:role/ci-dd-sdk-ios/' + +# Full description of secrets is available at https://datadoghq.atlassian.net/wiki/x/cIEB4w (internal) +# Keep this list and Confluence page up-to-date with every secret that is added to the list. +DD_IOS_SECRET__TEST_SECRET="test.secret" +DD_IOS_SECRET__GH_CLI_TOKEN="gh.cli.token" +DD_IOS_SECRET__CP_TRUNK_TOKEN="cocoapods.trunk.token" +DD_IOS_SECRET__SSH_KEY="ssh.key" + +declare -A DD_IOS_SECRETS=( + [0]="$DD_IOS_SECRET__TEST_SECRET | test secret to see if things work, free to change but not delete" + [1]="$DD_IOS_SECRET__GH_CLI_TOKEN | GitHub token to authenticate 'gh' cli (https://cli.github.com/)" + [2]="$DD_IOS_SECRET__CP_TRUNK_TOKEN | Cocoapods token to authenticate 'pod trunk' operations (https://guides.cocoapods.org/terminal/commands.html)" + [3]="$DD_IOS_SECRET__SSH_KEY | SSH key to authenticate 'git clone git@github.com:...' operations" +) diff --git a/tools/secrets/get-secret.sh b/tools/secrets/get-secret.sh new file mode 100755 index 0000000000..7e15c77645 --- /dev/null +++ b/tools/secrets/get-secret.sh @@ -0,0 +1,35 @@ +#!/bin/zsh + +source ./tools/utils/echo-color.sh +source ./tools/secrets/config.sh + +# Usage: +# get_secret +# +# Notes: +# - For use constants defined in 'tools/secrets/config.sh' +# - Requires `vault` to be installed +get_secret() { + local secret_name="$1" + + export VAULT_ADDR=$DD_VAULT_ADDR + if [ "$CI" = "true" ]; then + vault login -method=aws -no-print + else + if vault token lookup &>/dev/null; then + echo_succ "Reading '$secret_name' secret in local env. You are already authenticated with 'vault'." >&2 + else + echo_warn "Reading '$secret_name' secret in local env. You will now be authenticated with OIDC in your web browser." >&2 + vault login -method=oidc -no-print + fi + fi + + local secret_value=$(vault kv get -field=value "$DD_IOS_SECRETS_PATH_PREFIX/$secret_name") + + if [[ -z "$secret_value" ]]; then + echo_err "Error" "Failed to retrieve the '$secret_name' secret or the secret is empty." >&2 + exit 1 + fi + + echo $secret_value +} diff --git a/tools/secrets/set-secret.sh b/tools/secrets/set-secret.sh new file mode 100755 index 0000000000..7957230761 --- /dev/null +++ b/tools/secrets/set-secret.sh @@ -0,0 +1,99 @@ +#!/bin/zsh + +# Usage: +# $ ./tools/secrets/set-secret.sh +# +# Note: +# - Requires `vault` to be installed + +set -eo pipefail +source ./tools/utils/echo-color.sh +source ./tools/secrets/config.sh + +list_secrets() { + GREEN="\e[32m" + RESET="\e[0m" + + echo "Available secrets:" + for key in ${(k)DD_IOS_SECRETS}; do + IFS=" | " read -r name description <<< "${DD_IOS_SECRETS[$key]}" + echo "$key) ${GREEN}$name${RESET} - $description" + done | sort -n + + echo "" + echo "To add a new secret, first define it in 'tools/secrets/config.sh' and retry." +} + +select_secret() { + echo + while true; do + echo_info "Enter the number of the secret you want to set:" + read "secret_number" + if [[ -n ${DD_IOS_SECRETS[$secret_number]} ]]; then + IFS=" | " read -r SECRET_NAME SECRET_DESC <<< "${DD_IOS_SECRETS[$secret_number]}" + break + else + echo_err "Invalid selection. Please enter a valid number." + fi + done +} + +get_secret_value_from_input() { + echo_info "Enter the new value for '$SECRET_NAME':" + read "SECRET_VALUE" + echo +} + +get_secret_value_from_file() { + echo_info "Enter the file path to read the value for '$SECRET_NAME':" + read "SECRET_FILE" + echo + + SECRET_FILE=${SECRET_FILE/#\~/$HOME} # Expand ~ to home directory if present + echo_info "Using '$SECRET_FILE'" + + if [[ -f "$SECRET_FILE" ]]; then + SECRET_VALUE=$(cat "$SECRET_FILE") + else + echo_err "Error: File '$SECRET_FILE' does not exist." + exit 1 + fi +} + +select_input_method() { + echo + echo_info "How would you like to provide the secret value?" + echo "1) Enter manually" + echo "2) Read from a file" + while true; do + echo_info "Enter your choice (1 or 2):" + read "input_method" + case $input_method in + 1) + get_secret_value_from_input + break + ;; + 2) + get_secret_value_from_file + break + ;; + *) + echo_err "Invalid choice. Please enter 1 or 2." + ;; + esac + done +} + +set_secret_value() { + echo_info "You will now be authenticated with OIDC in your web browser. Press ENTER to continue." + read + export VAULT_ADDR=$DD_VAULT_ADDR + vault login -method=oidc -no-print + vault kv put "$DD_IOS_SECRETS_PATH_PREFIX/$SECRET_NAME" value="$SECRET_VALUE" + echo_succ "Secret '$SECRET_NAME' set successfully." +} + +list_secrets +select_secret +select_input_method +set_secret_value diff --git a/tools/smoke-test.sh b/tools/smoke-test.sh index a0a69fcde5..4e7448ec1d 100755 --- a/tools/smoke-test.sh +++ b/tools/smoke-test.sh @@ -12,8 +12,8 @@ set -eo pipefail source ./tools/utils/argparse.sh -source ./tools/utils/echo_color.sh -source ./tools/utils/current_git.sh +source ./tools/utils/echo-color.sh +source ./tools/utils/current-git.sh set_description "Executes smoke tests in the specified --test-directory, using the provided --os, --platform, and --device." define_arg "test-directory" "" "Specifies the directory where the smoke tests are located" "string" "true" diff --git a/tools/spm-build.sh b/tools/spm-build.sh index f11881b047..667d8bc888 100755 --- a/tools/spm-build.sh +++ b/tools/spm-build.sh @@ -9,7 +9,7 @@ # --scheme: Identifies the scheme to build set -eo pipefail -source ./tools/utils/echo_color.sh +source ./tools/utils/echo-color.sh source ./tools/utils/argparse.sh set_description "Builds SPM package for a specified --scheme and --destination." diff --git a/tools/tools-test.sh b/tools/tools-test.sh index 45fd8e7d25..eb9d12183a 100755 --- a/tools/tools-test.sh +++ b/tools/tools-test.sh @@ -5,7 +5,7 @@ # Runs tests for repo tools. set -eo pipefail -source ./tools/utils/echo_color.sh +source ./tools/utils/echo-color.sh test_swift_package() { local package_path="$1" diff --git a/tools/ui-test.sh b/tools/ui-test.sh index b2abbed603..a2bcd109c1 100755 --- a/tools/ui-test.sh +++ b/tools/ui-test.sh @@ -11,7 +11,7 @@ # --test-plan: Identifies the test plan to run set -eo pipefail -source ./tools/utils/echo_color.sh +source ./tools/utils/echo-color.sh source ./tools/utils/argparse.sh set_description "Executes UI tests for a specified --test-plan using the provided --os, --platform, and --device." diff --git a/tools/utils/common.mk b/tools/utils/common.mk index 130ad11037..a4713b84f8 100644 --- a/tools/utils/common.mk +++ b/tools/utils/common.mk @@ -10,12 +10,13 @@ ifndef REPO_ROOT $(error "REPO_ROOT is not set but it is required. It must resolve to the repo root folder.") endif -ECHO_TITLE=$(REPO_ROOT)/tools/utils/echo_color.sh --title -ECHO_SUBTITLE=$(REPO_ROOT)/tools/utils/echo_color.sh --subtitle -ECHO_SUBTITLE2=$(REPO_ROOT)/tools/utils/echo_color.sh --subtitle2 -ECHO_ERROR=$(REPO_ROOT)/tools/utils/echo_color.sh --err -ECHO_WARNING=$(REPO_ROOT)/tools/utils/echo_color.sh --warn -ECHO_SUCCESS=$(REPO_ROOT)/tools/utils/echo_color.sh --succ +ECHO_TITLE=$(REPO_ROOT)/tools/utils/echo-color.sh --title +ECHO_SUBTITLE=$(REPO_ROOT)/tools/utils/echo-color.sh --subtitle +ECHO_SUBTITLE2=$(REPO_ROOT)/tools/utils/echo-color.sh --subtitle2 +ECHO_INFO=$(REPO_ROOT)/tools/utils/echo-color.sh --info +ECHO_ERROR=$(REPO_ROOT)/tools/utils/echo-color.sh --err +ECHO_WARNING=$(REPO_ROOT)/tools/utils/echo-color.sh --warn +ECHO_SUCCESS=$(REPO_ROOT)/tools/utils/echo-color.sh --succ define require_param if [ -z "$${$(1)}" ]; then \ @@ -24,6 +25,6 @@ define require_param fi endef -CURRENT_GIT_TAG := $(shell $(REPO_ROOT)/tools/utils/current_git.sh --print-tag) -CURRENT_GIT_BRANCH := $(shell $(REPO_ROOT)/tools/utils/current_git.sh --print-branch) -CURRENT_GIT_REF := $(shell $(REPO_ROOT)/tools/utils/current_git.sh --print) +CURRENT_GIT_TAG := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print-tag) +CURRENT_GIT_BRANCH := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print-branch) +CURRENT_GIT_REF := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print) diff --git a/tools/utils/current_git.sh b/tools/utils/current-git.sh similarity index 96% rename from tools/utils/current_git.sh rename to tools/utils/current-git.sh index bcbd3a42f9..d71c057dd8 100755 --- a/tools/utils/current_git.sh +++ b/tools/utils/current-git.sh @@ -1,7 +1,7 @@ #!/bin/zsh # Usage: -# $ ./tools/utils/current_git.sh -h +# $ ./tools/utils/current-git.sh -h # Prints the current Git reference, either a tag or a branch name. # Options: diff --git a/tools/utils/echo_color.sh b/tools/utils/echo-color.sh similarity index 85% rename from tools/utils/echo_color.sh rename to tools/utils/echo-color.sh index 2a39f45421..89b31e55cb 100755 --- a/tools/utils/echo_color.sh +++ b/tools/utils/echo-color.sh @@ -1,11 +1,12 @@ #!/bin/zsh # Usage: -# ./tools/utils/echo_color.sh [OPTION] "message" [additional_message] +# ./tools/utils/echo-color.sh [OPTION] "message" [additional_message] # This script renders colored output for different types of log messages. -# It supports error, warning, success messages, and titles, each with distinctive coloring. +# It supports info, error, warning, success messages, and titles, each with distinctive coloring. # Options: +# --info Display info message in light blue. # --err Display the message as an error in red. # --warn Display the message as a warning in yellow. # --succ Display the message as a success in green. @@ -26,6 +27,10 @@ PURPLE="\e[35m" BLUE='\033[34m' BLUE_LIGHT='\033[36m' +echo_info() { + echo "${BLUE_LIGHT}$1${RESET} $2" +} + echo_err() { echo "${RED}$1${RESET} $2" } @@ -65,6 +70,9 @@ echo_subtitle2() { } case "$1" in + --info) + echo_info "$2" "$3" + ;; --err) echo_err "$2" "$3" ;; From 7ea60ea8b596be7cbcce3b693691fc407be4f028 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 5 Jul 2024 12:45:25 +0200 Subject: [PATCH 13/43] RUM-4079 Prettier --- .gitignore | 2 +- tools/env-check.sh | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index f2269ab32d..45e09e1325 100644 --- a/.gitignore +++ b/.gitignore @@ -21,4 +21,4 @@ __pycache__ *.pytest_cache # CI job artifacts -artifacts/ \ No newline at end of file +artifacts/ diff --git a/tools/env-check.sh b/tools/env-check.sh index 664f14240a..aaca67117f 100755 --- a/tools/env-check.sh +++ b/tools/env-check.sh @@ -82,8 +82,7 @@ if [ "$CI" = "true" ]; then # Check if all secrets are available: ./tools/secrets/check-secrets.sh - echo "" - echo_succ "CI env:" + echo_subtitle "Print CI env" echo "▸ CI_COMMIT_TAG = ${CI_COMMIT_TAG:-(not set or empty)}" echo "▸ CI_COMMIT_BRANCH = ${CI_COMMIT_BRANCH:-(not set or empty)}" echo "▸ CI_COMMIT_SHA = ${CI_COMMIT_SHA:-(not set or empty)}" From d25442a059de8e2416822d32eb397ec528149ced Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 5 Jul 2024 15:42:48 +0200 Subject: [PATCH 14/43] RUM-4079 Cleanup --- .gitlab-ci.yml | 5 +++-- Makefile | 8 ++++++++ bitrise.yml | 2 -- tools/env-check.sh | 2 ++ 4 files changed, 13 insertions(+), 4 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 77be40c64e..ba626848a1 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -131,7 +131,8 @@ UI Tests: Tools Tests: stage: test rules: - - changes: + - if: '$CI_COMMIT_BRANCH' # when on branch with following changes compared to develop + changes: paths: - "tools/**/*" - "Makefile" @@ -230,7 +231,7 @@ Build Artifacts: artifacts: paths: - artifacts - expire_in: 3 days # TODO: RUM-4079 extend to 6 weeks + expire_in: 4 weeks before_script: - *export_MAKE_release_params script: diff --git a/Makefile b/Makefile index 507963c2b4..829ad4ab55 100644 --- a/Makefile +++ b/Makefile @@ -338,12 +338,14 @@ e2e-monitors-generate: @./tools/nightly-e2e-tests/nightly_e2e.py generate-tf --tests-dir ../../Datadog/E2ETests @echo "⚠️ Remember to delete all iOS monitors manually from Mobile-Integration org before running 'terraform apply'." +# Builds release artifacts for given tag release-build: @$(call require_param,GIT_TAG) @$(call require_param,ARTIFACTS_PATH) @$(ECHO_TITLE) "make release-build GIT_TAG='$(GIT_TAG)' ARTIFACTS_PATH='$(ARTIFACTS_PATH)'" ./tools/release/build.sh --tag "$(GIT_TAG)" --artifacts-path "$(ARTIFACTS_PATH)" +# Validate release artifacts for given tag release-validate: @$(call require_param,GIT_TAG) @$(call require_param,ARTIFACTS_PATH) @@ -351,6 +353,7 @@ release-validate: ./tools/release/validate-version.sh --artifacts-path "$(ARTIFACTS_PATH)" --tag "$(GIT_TAG)" ./tools/release/validate-xcframeworks.sh --artifacts-path "$(ARTIFACTS_PATH)" +# Publish GitHub asset to GH release release-publish-github: @$(call require_param,GIT_TAG) @$(call require_param,ARTIFACTS_PATH) @@ -361,6 +364,7 @@ release-publish-github: --artifacts-path "$(ARTIFACTS_PATH)" \ --tag "$(GIT_TAG)" +# Publish Cocoapods podspec to trunk release-publish-podspec: @$(call require_param,PODSPEC_NAME) @$(call require_param,ARTIFACTS_PATH) @@ -370,9 +374,11 @@ release-publish-podspec: --artifacts-path "$(ARTIFACTS_PATH)" \ --podspec-name "$(PODSPEC_NAME)" +# Publish DatadogInternal podspec release-publish-internal-podspecs: @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogInternal.podspec" +# Publish podspecs that depend on DatadogInternal release-publish-dependent-podspecs: @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogCore.podspec" @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogLogs.podspec" @@ -382,6 +388,7 @@ release-publish-dependent-podspecs: @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogCrashReporting.podspec" @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogWebViewTracking.podspec" +# Publish legacy podspecs release-publish-legacy-podspecs: @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogObjc.podspec" @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogAlamofireExtension.podspec" @@ -390,6 +397,7 @@ release-publish-legacy-podspecs: @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogSDKCrashReporting.podspec" @$(MAKE) release-publish-podspec PODSPEC_NAME="DatadogSDKAlamofireExtension.podspec" +# Set ot update CI secrets set-ci-secret: @$(ECHO_TITLE) "make set-ci-secret" @./tools/secrets/set-secret.sh diff --git a/bitrise.yml b/bitrise.yml index a8b87a46a9..5f18db482b 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -52,8 +52,6 @@ workflows: after_run: - _make_dependencies - run_conditioned_workflows - - _deploy_artifacts - - _notify_failure_on_slack _make_dependencies: description: |- diff --git a/tools/env-check.sh b/tools/env-check.sh index aaca67117f..089833b861 100755 --- a/tools/env-check.sh +++ b/tools/env-check.sh @@ -86,4 +86,6 @@ if [ "$CI" = "true" ]; then echo "▸ CI_COMMIT_TAG = ${CI_COMMIT_TAG:-(not set or empty)}" echo "▸ CI_COMMIT_BRANCH = ${CI_COMMIT_BRANCH:-(not set or empty)}" echo "▸ CI_COMMIT_SHA = ${CI_COMMIT_SHA:-(not set or empty)}" + echo "▸ RELEASE_GIT_TAG = ${RELEASE_GIT_TAG:-(not set or empty)}" + echo "▸ RELEASE_DRY_RUN = ${RELEASE_DRY_RUN:-(not set or empty)}" fi From 3a050788b9d6d1666b11255ccdff05f7a41a156a Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 9 Jul 2024 13:52:22 +0200 Subject: [PATCH 15/43] RUM-4079 Clean repo artifact using `git clean -fxd` --- tools/release/build-xcframeworks.sh | 38 ++++++----------------------- 1 file changed, 8 insertions(+), 30 deletions(-) diff --git a/tools/release/build-xcframeworks.sh b/tools/release/build-xcframeworks.sh index df05c77216..0b23465862 100755 --- a/tools/release/build-xcframeworks.sh +++ b/tools/release/build-xcframeworks.sh @@ -23,37 +23,17 @@ define_arg "output-path" "" "The path to the output directory where XCFrameworks check_for_help "$@" parse_args "$@" -rm -rf "$output_path" -mkdir -p "$output_path" REPO_PATH=$(realpath "$repo_path") -XCFRAMEWORKS_OUTPUT=$(realpath "$output_path") -ARCHIVES_TEMP_OUTPUT="$XCFRAMEWORKS_OUTPUT/archives" - -check_repo_clean_state() { - echo_subtitle2 "Check repo at '$REPO_PATH'" - git diff-index --quiet HEAD -- || { echo_err "Error:" "Repository has uncommitted changes."; exit 1; } - - [ -d "Datadog.xcworkspace" ] && echo_succ "Found 'Datadog.xcworkspace' in '$REPO_PATH'" \ - || { echo_err "Error:" "Could not find 'Datadog.xcworkspace' in '$REPO_PATH'." ; exit 1; } +echo_info "Clean '$REPO_PATH' with 'git clean -fxd'" +cd "$REPO_PATH" && git clean -fxd && cd - - [ -f "Cartfile" ] && echo_succ "Found 'Cartfile' in '$REPO_PATH'" \ - || { echo_err "Error:" "Could not find 'Cartfile' in '$REPO_PATH'." ; exit 1; } +echo_info "Create '$output_path'" +rm -rf "$output_path" && mkdir -p "$output_path" - [ -f "Cartfile.resolved" ] && echo_succ "Found 'Cartfile.resolved' in '$REPO_PATH'" \ - || { echo_err "Error:" "Could not find 'Cartfile.resolved' in '$REPO_PATH'." ; exit 1; } - - local config_files=$(find . -name "*.local.xcconfig") - if [[ -n $config_files ]]; then - echo_err "Error:" "The repo at '$REPO_PATH' is not in a clean state." - echo "It has following local config files:" - echo "$config_files" | awk '{print "- " $0}' - exit 1 - else - echo_succ "The repository is in a clean state and no '*.local.xcconfig' files are present." - fi -} +XCFRAMEWORKS_OUTPUT=$(realpath "$output_path") +ARCHIVES_TEMP_OUTPUT="$XCFRAMEWORKS_OUTPUT/archives" archive() { local scheme="$1" @@ -83,7 +63,7 @@ build_xcframework() { echo_subtitle2 "Build '$product.xcframework' using platform='$platform'" if [[ $platform == *"iOS"* ]]; then - echo "▸ Archive $product iOS" + echo_info "▸ Archive $product iOS" archive "$product iOS" "generic/platform=iOS" "$ARCHIVES_TEMP_OUTPUT/$product/ios" xcoptions+=(-archive "$ARCHIVES_TEMP_OUTPUT/$product/ios.xcarchive" -framework "$product.framework") @@ -93,7 +73,7 @@ build_xcframework() { fi if [[ $platform == *"tvOS"* ]]; then - echo "▸ Archive $product tvOS" + echo_info "▸ Archive $product tvOS" archive "$product tvOS" "generic/platform=tvOS" "$ARCHIVES_TEMP_OUTPUT/$product/tvos" xcoptions+=(-archive "$ARCHIVES_TEMP_OUTPUT/$product/tvos.xcarchive" -framework "$product.framework") @@ -114,8 +94,6 @@ build_xcframework() { echo_info "cd '$REPO_PATH'" cd $REPO_PATH -check_repo_clean_state - # Select PLATFORMS to build ('iOS' | 'tvOS' | 'iOS,tvOS') PLATFORMS="" [[ "$ios" == "true" ]] && PLATFORMS+="iOS" From 41ddd33b4e812c991bbaab0b741e696d7a14fac2 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 10 Jul 2024 11:16:53 +0200 Subject: [PATCH 16/43] chore: make testing distributed header injection easier in Example app --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 + ...ugManualTraceInjectionViewController.swift | 112 +++++++++++------- Datadog/Example/Utils/MultiSelector.swift | 85 +++++++++++++ .../TraceContextInjection.swift | 2 +- 4 files changed, 164 insertions(+), 41 deletions(-) create mode 100644 Datadog/Example/Utils/MultiSelector.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index ebda4a4175..639d1ac330 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -53,6 +53,8 @@ 3C5D636D2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */; }; 3C5D691F2B76825500C4E07E /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; }; 3C5D69222B76826000C4E07E /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; }; + 3C62C3612C3E852F00C7E336 /* MultiSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C62C3602C3E852F00C7E336 /* MultiSelector.swift */; }; + 3C62C3622C3E852F00C7E336 /* MultiSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C62C3602C3E852F00C7E336 /* MultiSelector.swift */; }; 3C6C7FE72B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; 3C6C7FE82B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; 3C6C7FE92B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; }; @@ -2105,6 +2107,7 @@ 3C43A3862C188970000BFB21 /* WatchdogTerminationMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchdogTerminationMonitorTests.swift; sourceTree = ""; }; 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+Datadog.swift"; sourceTree = ""; }; 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+DatadogTests.swift"; sourceTree = ""; }; + 3C62C3602C3E852F00C7E336 /* MultiSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelector.swift; sourceTree = ""; }; 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpan.swift; sourceTree = ""; }; 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OTelSpanBuilder.swift; sourceTree = ""; }; 3C6C7FE22B459AAA006F5CBC /* OTelTraceId+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceId+Datadog.swift"; sourceTree = ""; }; @@ -4616,6 +4619,7 @@ 61441C902461A648003D8BB8 /* ConsoleOutputInterceptor.swift */, 61441C912461A648003D8BB8 /* UIButton+Disabling.swift */, D2F44FC1299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift */, + 3C62C3602C3E852F00C7E336 /* MultiSelector.swift */, ); path = Utils; sourceTree = ""; @@ -8411,6 +8415,7 @@ 618236892710560900125326 /* DebugWebviewViewController.swift in Sources */, 61F74AF426F20E4600E5F5ED /* DebugCrashReportingWithRUMViewController.swift in Sources */, 1434A4662B7F8D880072E3BB /* DebugOTelTracingViewController.swift in Sources */, + 3C62C3612C3E852F00C7E336 /* MultiSelector.swift in Sources */, 61E5333824B84EE2003D6C4E /* DebugRUMViewController.swift in Sources */, 61441C0524616DE9003D8BB8 /* ExampleAppDelegate.swift in Sources */, 61020C2C2758E853005EEAEA /* DebugBackgroundEventsViewController.swift in Sources */, @@ -8820,6 +8825,7 @@ files = ( 1434A4672B7F8D880072E3BB /* DebugOTelTracingViewController.swift in Sources */, D2F44FC3299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift in Sources */, + 3C62C3622C3E852F00C7E336 /* MultiSelector.swift in Sources */, D240680827CE6C9E00C04F44 /* ConsoleOutputInterceptor.swift in Sources */, D240681E27CE6C9E00C04F44 /* ExampleAppDelegate.swift in Sources */, D240682B27CE6C9E00C04F44 /* UIButton+Disabling.swift in Sources */, diff --git a/Datadog/Example/Debugging/DebugManualTraceInjectionViewController.swift b/Datadog/Example/Debugging/DebugManualTraceInjectionViewController.swift index 3a6fcaf15c..916c92ae60 100644 --- a/Datadog/Example/Debugging/DebugManualTraceInjectionViewController.swift +++ b/Datadog/Example/Debugging/DebugManualTraceInjectionViewController.swift @@ -16,18 +16,32 @@ internal class DebugManualTraceInjectionViewController: UIHostingController String { + switch self { + case .all: + return "All" + case .sampled: + return "Sampled" + } + } +} + @available(iOS 14.0, *) internal struct DebugManualTraceInjectionView: View { - enum TraceHeaderType: String, CaseIterable { + enum TraceHeaderType: String, CaseIterable, Identifiable { case datadog = "Datadog" case w3c = "W3C" case b3Single = "B3-Single" case b3Multiple = "B3-Multiple" + + var id: String { rawValue } } @State private var spanName = "network request" - @State private var requestURL = "http://127.0.0.1:8000" - @State private var selectedTraceHeaderType: TraceHeaderType = .datadog + @State private var requestURL = "https://httpbin.org/get" + @State private var selectedTraceHeaderTypes: Set = [.datadog, .w3c] + @State private var selectedTraceContextInjection: TraceContextInjection = .all @State private var sampleRate: Float = 100.0 @State private var isRequestPending = false @@ -59,12 +73,18 @@ internal struct DebugManualTraceInjectionView: View { Section(header: Text("Span name:")) { TextField("", text: $spanName) } - Picker("Trace header type:", selection: $selectedTraceHeaderType) { - ForEach(TraceHeaderType.allCases, id: \.self) { headerType in - Text(headerType.rawValue) + Picker("Trace context injection:", selection: $selectedTraceContextInjection) { + ForEach(TraceContextInjection.allCases, id: \.self) { headerType in + Text(headerType.toString()) } } .pickerStyle(.inline) + MultiSelector( + label: Text("Trace header type:"), + options: TraceHeaderType.allCases, + optionToString: { $0.rawValue }, + selected: $selectedTraceHeaderTypes + ) Section(header: Text("Trace sample Rate")) { Slider( value: $sampleRate, @@ -101,42 +121,44 @@ internal struct DebugManualTraceInjectionView: View { } var request = URLRequest(url: url) - request.httpMethod = "POST" + request.httpMethod = "GET" let span = Tracer.shared().startRootSpan(operationName: spanName) - switch selectedTraceHeaderType { - case .datadog: - let writer = HTTPHeadersWriter( - samplingStrategy: .custom(sampleRate: sampleRate), - traceContextInjection: .all - ) - Tracer.shared().inject(spanContext: span.context, writer: writer) - writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } - case .w3c: - let writer = W3CHTTPHeadersWriter( - samplingStrategy: .custom(sampleRate: sampleRate), - tracestate: [:], - traceContextInjection: .all - ) - Tracer.shared().inject(spanContext: span.context, writer: writer) - writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } - case .b3Single: - let writer = B3HTTPHeadersWriter( - samplingStrategy: .custom(sampleRate: sampleRate), - injectEncoding: .single, - traceContextInjection: .all - ) - Tracer.shared().inject(spanContext: span.context, writer: writer) - writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } - case .b3Multiple: - let writer = B3HTTPHeadersWriter( - samplingStrategy: .custom(sampleRate: sampleRate), - injectEncoding: .multiple, - traceContextInjection: .all - ) - Tracer.shared().inject(spanContext: span.context, writer: writer) - writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } + for selectedTraceHeaderType in selectedTraceHeaderTypes { + switch selectedTraceHeaderType { + case .datadog: + let writer = HTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: sampleRate), + traceContextInjection: selectedTraceContextInjection + ) + Tracer.shared().inject(spanContext: span.context, writer: writer) + writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } + case .w3c: + let writer = W3CHTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: sampleRate), + tracestate: [:], + traceContextInjection: selectedTraceContextInjection + ) + Tracer.shared().inject(spanContext: span.context, writer: writer) + writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } + case .b3Single: + let writer = B3HTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: sampleRate), + injectEncoding: .single, + traceContextInjection: selectedTraceContextInjection + ) + Tracer.shared().inject(spanContext: span.context, writer: writer) + writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } + case .b3Multiple: + let writer = B3HTTPHeadersWriter( + samplingStrategy: .custom(sampleRate: sampleRate), + injectEncoding: .multiple, + traceContextInjection: selectedTraceContextInjection + ) + Tracer.shared().inject(spanContext: span.context, writer: writer) + writer.traceHeaderFields.forEach { request.setValue($0.value, forHTTPHeaderField: $0.key) } + } } send(request: request) { @@ -147,7 +169,17 @@ internal struct DebugManualTraceInjectionView: View { private func send(request: URLRequest, completion: @escaping () -> Void) { isRequestPending = true - let task = session.dataTask(with: request) { _, _, _ in + let task = session.dataTask(with: request) { data, response, _ in + let httpResponse = response as! HTTPURLResponse + print("🚀 Request completed with status code: \(httpResponse.statusCode)") + + // pretty print response + if let data = data { + let json = try? JSONSerialization.jsonObject(with: data, options: []) + if let json = json { + print("🚀 Response: \(json)") + } + } completion() DispatchQueue.main.async { self.isRequestPending = false } } diff --git a/Datadog/Example/Utils/MultiSelector.swift b/Datadog/Example/Utils/MultiSelector.swift new file mode 100644 index 0000000000..aa2ec5ee9c --- /dev/null +++ b/Datadog/Example/Utils/MultiSelector.swift @@ -0,0 +1,85 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import SwiftUI + +@available(iOS 14.0, *) +struct MultiSelector: View { + let label: LabelView + let options: [Selectable] + let optionToString: (Selectable) -> String + + var selected: Binding> + + private var formattedSelectedListString: String { + ListFormatter.localizedString( + byJoining: selected.wrappedValue.map { + optionToString($0) + } + ) + } + + var body: some View { + NavigationLink(destination: multiSelectionView()) { + HStack { + label + Spacer() + Text(formattedSelectedListString) + .foregroundColor(.gray) + .multilineTextAlignment(.trailing) + } + } + } + + private func multiSelectionView() -> some View { + MultiSelectionView( + options: options, + optionToString: optionToString, + selected: selected + ) + } +} + + +@available(iOS 13.0, *) +struct MultiSelectionView: View { + let options: [Selectable] + let optionToString: (Selectable) -> String + + @Binding var selected: Set + + var body: some View { + List { + ForEach(options) { selectable in + Button(action: { + toggleSelection(selectable: selectable) + }) { + HStack { + Text(optionToString(selectable)) + .foregroundColor(.black) + Spacer() + if selected.contains(where: { + $0.id == selectable.id + }) { + Image(systemName: "checkmark") + .foregroundColor(.accentColor) + } + } + } + .tag(selectable.id) + } + } + .listStyle(GroupedListStyle()) + } + + private func toggleSelection(selectable: Selectable) { + if let existingIndex = selected.firstIndex(where: { $0.id == selectable.id }) { + selected.remove(at: existingIndex) + } else { + selected.insert(selectable) + } + } +} diff --git a/DatadogInternal/Sources/NetworkInstrumentation/TraceContextInjection.swift b/DatadogInternal/Sources/NetworkInstrumentation/TraceContextInjection.swift index b890b32ceb..b134a4683c 100644 --- a/DatadogInternal/Sources/NetworkInstrumentation/TraceContextInjection.swift +++ b/DatadogInternal/Sources/NetworkInstrumentation/TraceContextInjection.swift @@ -7,7 +7,7 @@ import Foundation /// Defines whether the trace context should be injected into all requests or only sampled ones. -public enum TraceContextInjection { +public enum TraceContextInjection: CaseIterable { /// Injects trace context into all requests irrespective of the sampling decision. case all From 85dd33ce8ba6c4f4d41d49a3bf7f643aa93aa9e9 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 9 Jul 2024 17:20:09 +0200 Subject: [PATCH 17/43] RUM-4079 Migrate SRSnapshoTests to GitLab --- .gitlab-ci.yml | 21 +++++ Makefile | 35 +++++--- tools/sr-snapshot-test.sh | 84 +++++++++++++++++++ .../sr-snapshots/Sources/Git/GitClient.swift | 23 +---- .../Sources/SRSnapshotsCore/Commands.swift | 4 +- 5 files changed, 135 insertions(+), 32 deletions(-) create mode 100755 tools/sr-snapshot-test.sh diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index ba626848a1..4c95390e1c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -128,6 +128,27 @@ UI Tests: - make clean repo-setup ENV=ci - make ui-test TEST_PLAN="$TEST_PLAN" OS="$OS" PLATFORM="$PLATFORM" DEVICE="$DEVICE" +SR Snapshot Tests: + stage: ui-test + rules: + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] + variables: + XCODE: "15.4.0" + OS: "17.5" + PLATFORM: "iOS Simulator" + DEVICE: "iPhone 15" + ARTIFACTS_PATH: "artifacts" + artifacts: + paths: + - artifacts + expire_in: 1 week + when: on_failure + script: + - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --os "$OS" --ssh # temporary, waiting for AMI + - make clean repo-setup ENV=ci + - make sr-pull-snapshots sr-snapshot-test OS="$OS" PLATFORM="$PLATFORM" DEVICE="$DEVICE" ARTIFACTS_PATH="$ARTIFACTS_PATH" + Tools Tests: stage: test rules: diff --git a/Makefile b/Makefile index 829ad4ab55..195658659c 100644 --- a/Makefile +++ b/Makefile @@ -3,6 +3,7 @@ all: env-check repo-setup templates lint license-check \ test test-ios test-ios-all test-tvos test-tvos-all \ ui-test ui-test-all ui-test-podinstall \ + sr-snapshot-test sr-pull-snapshots sr-push-snapshots \ tools-test \ smoke-test smoke-test-ios smoke-test-ios-all smoke-test-tvos smoke-test-tvos-all \ spm-build spm-build-ios spm-build-tvos spm-build-visionos spm-build-macos \ @@ -104,6 +105,14 @@ DEFAULT_TVOS_OS := latest DEFAULT_TVOS_PLATFORM := tvOS Simulator DEFAULT_TVOS_DEVICE := Apple TV +# Test env for running SR snapshot tests in local: +DEFAULT_SR_SNAPSHOT_TESTS_OS := 17.5 +DEFAULT_SR_SNAPSHOT_TESTS_PLATFORM := iOS Simulator +DEFAULT_SR_SNAPSHOT_TESTS_DEVICE := iPhone 15 + +# Default location for deploying artifacts +DEFAULT_ARTIFACTS_PATH := artifacts + # Run unit tests for specified SCHEME test: @$(call require_param,SCHEME) @@ -289,19 +298,25 @@ sr-models-generate: sr-models-verify: @$(MAKE) models-verify PRODUCT="sr" +# Pushes current SR snapshots to snapshots repo sr-push-snapshots: - @echo "🎬 ↗️ Pushing SR snapshots to remote repo..." - @cd tools/sr-snapshots && swift run sr-snapshots push \ - --local-folder ../../DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_ \ - --remote-folder ../../../dd-mobile-session-replay-snapshots \ - --remote-branch "main" + @$(ECHO_TITLE) "make sr-push-snapshots" + ./tools/sr-snapshot-test.sh --push +# Pulls SR snapshots from snapshots repo sr-pull-snapshots: - @echo "🎬 ↙️ Pulling SR snapshots from remote repo..." - @cd tools/sr-snapshots && swift run sr-snapshots pull \ - --local-folder ../../DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_ \ - --remote-folder ../../../dd-mobile-session-replay-snapshots \ - --remote-branch "main" + @$(ECHO_TITLE) "make sr-pull-snapshots" + ./tools/sr-snapshot-test.sh --pull + +# Run Session Replay snapshot tests +sr-snapshot-test: + @:$(eval OS ?= $(DEFAULT_SR_SNAPSHOT_TESTS_OS)) + @:$(eval PLATFORM ?= $(DEFAULT_SR_SNAPSHOT_TESTS_PLATFORM)) + @:$(eval DEVICE ?= $(DEFAULT_SR_SNAPSHOT_TESTS_DEVICE)) + @:$(eval ARTIFACTS_PATH ?= $(DEFAULT_ARTIFACTS_PATH)) + @$(ECHO_TITLE) "make sr-snapshot-test OS='$(OS)' PLATFORM='$(PLATFORM)' DEVICE='$(DEVICE)' ARTIFACTS_PATH='$(ARTIFACTS_PATH)'" + ./tools/sr-snapshot-test.sh \ + --test --os "$(OS)" --device "$(DEVICE)" --platform "$(PLATFORM)" --artifacts-path "$(ARTIFACTS_PATH)" # Generate api-surface files for Datadog and DatadogObjc. api-surface: diff --git a/tools/sr-snapshot-test.sh b/tools/sr-snapshot-test.sh new file mode 100755 index 0000000000..a442010298 --- /dev/null +++ b/tools/sr-snapshot-test.sh @@ -0,0 +1,84 @@ +#!/bin/zsh + +set -eo pipefail +source ./tools/utils/argparse.sh +source ./tools/utils/echo-color.sh + +set_description "." +define_arg "pull" "false" "..." "store_true" +define_arg "push" "false" "..." "store_true" +define_arg "open-project" "false" "..." "store_true" +define_arg "test" "false" "..." "store_true" +define_arg "os" "" "Sets the operating system version for the tests, e.g. '17.5'" "string" "false" +define_arg "platform" "" "Defines the type of simulator platform for the tests, e.g. 'iOS Simulator'" "string" "false" +define_arg "device" "" "Specifies the simulator device for running tests, e.g. 'iPhone 15 Pro'" "string" "false" +define_arg "artifacts-path" "" "Path to artifacts for failed tests." "string" "false" + +check_for_help "$@" +parse_args "$@" + +REPO_ROOT=$(realpath .) + +SNAPSHOTS_CLI_PATH="$REPO_ROOT/tools/sr-snapshots" +SNAPSHOTS_DIR="$REPO_ROOT/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests/_snapshots_" +SNAPSHOTS_REPO_PATH="$REPO_ROOT/../dd-mobile-session-replay-snapshots" + +TEST_SCHEME="SRSnapshotTests" +TEST_WORKSPACE="$REPO_ROOT/DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests.xcworkspace" +TEST_ARTIFACTS_PATH="$REPO_ROOT/$artifacts_path/sr-snapshot-tests" + +pull_snapshots() { + echo_subtitle "Pull SR snapshots to '$SNAPSHOTS_DIR'" + cd "$SNAPSHOTS_CLI_PATH" + swift run sr-snapshots pull \ + --local-folder "$SNAPSHOTS_DIR" \ + --remote-folder "$SNAPSHOTS_REPO_PATH" \ + --remote-branch "main" + cd - +} + +push_snapshots() { + echo_subtitle "Push SR snapshots through '$SNAPSHOTS_REPO_PATH'" + cd "$SNAPSHOTS_CLI_PATH" + swift run sr-snapshots push \ + --local-folder "$SNAPSHOTS_DIR" \ + --remote-folder "$SNAPSHOTS_REPO_PATH" \ + --remote-branch "main" + cd - +} + +test_snapshots() { + local destination="platform=$platform,name=$device,OS=$os" + echo_subtitle "Test SR snapshots using destination='$destination'" + + rm -rf "$TEST_ARTIFACTS_PATH" + mkdir -p "$TEST_ARTIFACTS_PATH" + + export DD_TEST_UTILITIES_ENABLED=1 # it is used in `dd-sdk-ios/Package.swift` to enable `TestUtilities` module + xcodebuild -version + xcodebuild -workspace "$TEST_WORKSPACE" -destination "$destination" -scheme "$TEST_SCHEME" -resultBundlePath "$TEST_ARTIFACTS_PATH/$TEST_SCHEME.xcresult" test | xcbeautify +} + +echo_info "Using" +echo_info "▸ SNAPSHOTS_CLI_PATH = '$SNAPSHOTS_CLI_PATH'" +echo_info "▸ SNAPSHOTS_PATH = '$SNAPSHOTS_DIR'" +echo_info "▸ SNAPSHOTS_REPO_PATH = '$SNAPSHOTS_REPO_PATH'" +echo_info "▸ TEST_SCHEME = '$TEST_SCHEME'" +echo_info "▸ TEST_WORKSPACE = '$TEST_WORKSPACE'" +echo_info "▸ TEST_ARTIFACTS_PATH = '$TEST_ARTIFACTS_PATH'" + +if [ "$pull" = "true" ]; then + pull_snapshots +fi + +if [ "$push" = "true" ]; then + push_snapshots +fi + +if [ "$test" = "true" ]; then + if [[ -z "$os" || -z "$device" || -z "$platform" || -z "$artifacts_path" ]]; then + echo_err "Error:" "--os, --device, --platform and --artifacts-path must be set along with --test." + exit 1 + fi + test_snapshots +fi diff --git a/tools/sr-snapshots/Sources/Git/GitClient.swift b/tools/sr-snapshots/Sources/Git/GitClient.swift index fd9878d435..5d86142077 100644 --- a/tools/sr-snapshots/Sources/Git/GitClient.swift +++ b/tools/sr-snapshots/Sources/Git/GitClient.swift @@ -37,11 +37,8 @@ public struct NOPGitClient: GitClient { public func push() throws {} } -/// Naive implementation of basic git client that uses GitHub CLI under the hood. -/// -/// It executes git commands directly in shell and requires `gh` CLI to be preinstalled on host (https://cli.github.com/) -/// and authorised for cloning private repositories. -public class GitHubGitClient: GitClient { +/// Basic git client +public class BasicGitClient: GitClient { /// Repo's SSH for git clone. private let ssh: String /// The name of git branch that this client will operate on. @@ -79,22 +76,8 @@ public class GitHubGitClient: GitClient { print("ℹ️ Repo does not exist and will be cloned to \(repoDirectory.path())") } - print("ℹ️ Checking if `gh` CLI is installed") - guard try cli.shellResult("which gh").status == 0 else { - throw GitClientError( - description: """ - `GitHubGitClient` requires `gh` CLI to be preinstalled and authorised on host. - Download it from https://cli.github.com/ - """ - ) - } - print("ℹ️ OK") - - print("ℹ️ Checking if `gh` CLI is authorised:") - try cli.shell("gh auth status") - print("ℹ️ Cloning repo (branch: '\(branch)'):") - try cli.shell("gh repo clone \(ssh) '\(repoDirectory.path())' -- --branch \(branch) --single-branch") + try cli.shell("git clone --branch \(branch) --single-branch \(ssh) '\(repoDirectory.path())'") self.repoDirectory = repoDirectory } diff --git a/tools/sr-snapshots/Sources/SRSnapshotsCore/Commands.swift b/tools/sr-snapshots/Sources/SRSnapshotsCore/Commands.swift index 7bceb58be6..6e91eb62c0 100644 --- a/tools/sr-snapshots/Sources/SRSnapshotsCore/Commands.swift +++ b/tools/sr-snapshots/Sources/SRSnapshotsCore/Commands.swift @@ -61,7 +61,7 @@ public struct PullSnapshotsCommand: ParsableCommand { public init() {} public func run() throws { - let git: GitClient = options.dryRun ? NOPGitClient() : GitHubGitClient(ssh: ssh, branch: options.remoteBranch) + let git: GitClient = options.dryRun ? NOPGitClient() : BasicGitClient(ssh: ssh, branch: options.remoteBranch) try git.cloneIfNeeded(to: options.remoteRepoFolder) let remoteRepo = try RemoteRepo(options: options, git: git) let localRepo = try LocalRepo(options: options) @@ -81,7 +81,7 @@ public struct PushSnapshotsCommand: ParsableCommand { public init() {} public func run() throws { - let git: GitClient = options.dryRun ? NOPGitClient() : GitHubGitClient(ssh: ssh, branch: options.remoteBranch) + let git: GitClient = options.dryRun ? NOPGitClient() : BasicGitClient(ssh: ssh, branch: options.remoteBranch) try git.cloneIfNeeded(to: options.remoteRepoFolder) let remoteRepo = try RemoteRepo(options: options, git: git) let localRepo = try LocalRepo(options: options) From ffd0140ce4a7dd1b4f3f44c52f0d4c051c6647d2 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 9 Jul 2024 17:20:16 +0200 Subject: [PATCH 18/43] RUM-4079 Prettier --- tools/release/build-xcframeworks.sh | 10 +++++----- tools/release/publish-github.sh | 4 ++-- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/tools/release/build-xcframeworks.sh b/tools/release/build-xcframeworks.sh index 0b23465862..94adc3f3e4 100755 --- a/tools/release/build-xcframeworks.sh +++ b/tools/release/build-xcframeworks.sh @@ -99,11 +99,11 @@ PLATFORMS="" [[ "$ios" == "true" ]] && PLATFORMS+="iOS" [[ "$tvos" == "true" ]] && { [ -n "$PLATFORMS" ] && PLATFORMS+=","; PLATFORMS+="tvOS"; } -echo_info "Building xcframeworks:" -echo_info "- REPO_PATH = '$REPO_PATH'" -echo_info "- ARCHIVES_TEMP_OUTPUT = '$ARCHIVES_TEMP_OUTPUT'" -echo_info "- XCFRAMEWORKS_OUTPUT = '$XCFRAMEWORKS_OUTPUT'" -echo_info "- PLATFORMS = '$PLATFORMS'" +echo_info "Building xcframeworks" +echo_info "▸ REPO_PATH = '$REPO_PATH'" +echo_info "▸ ARCHIVES_TEMP_OUTPUT = '$ARCHIVES_TEMP_OUTPUT'" +echo_info "▸ XCFRAMEWORKS_OUTPUT = '$XCFRAMEWORKS_OUTPUT'" +echo_info "▸ PLATFORMS = '$PLATFORMS'" # Build third-party XCFrameworks echo_subtitle2 "Run 'carthage bootstrap --platform $PLATFORMS --use-xcframeworks'" diff --git a/tools/release/publish-github.sh b/tools/release/publish-github.sh index 62486f68a9..0b778c9af8 100755 --- a/tools/release/publish-github.sh +++ b/tools/release/publish-github.sh @@ -55,8 +55,8 @@ upload() { } echo_info "Publishing '$GH_ASSET_PATH' to '$tag' release in '$REPO_NAME'" -echo "▸ Using DRY_RUN = $DRY_RUN" -echo "▸ Using OVERWRITE_EXISTING = $OVERWRITE_EXISTING" +echo_info "▸ Using DRY_RUN = $DRY_RUN" +echo_info "▸ Using OVERWRITE_EXISTING = $OVERWRITE_EXISTING" authenticate upload From d849bd7ff94a0fc80f9faabb4da4caac7ca750b2 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 9 Jul 2024 17:25:10 +0200 Subject: [PATCH 19/43] RUM-4079 Disintegrate SR Snapshot Tests in Bitrise --- bitrise.yml | 84 +++++------------------------------------------------ 1 file changed, 8 insertions(+), 76 deletions(-) diff --git a/bitrise.yml b/bitrise.yml index 5f18db482b..113da244e4 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -7,28 +7,16 @@ project_type: other workflows: push_to_pull_request: - description: |- - This workflow is triggered on starting new PR or pushing new changes to existing PRs. - By default, it doesn't run any test phases, but this behaviour is overwritten in `choose_workflows.py` when: - - one or more `DD_OVERWRITE_RUN_(phase)_TESTS` ENVs are passed to the current CI job: - - DD_OVERRIDE_RUN_SR_UNIT_TESTS='1' to run unit tests phase for Session Replay product - - a phase is selected on the checklist in the PR description, - - the PR changes a file which matches phase filter (e.g. changing a file in `Sources/*` will trigger unit tests phase) - envs: - - DD_RUN_SR_UNIT_TESTS: '0' - after_run: - - _make_dependencies - - run_conditioned_workflows - - _deploy_artifacts + steps: + - script: + inputs: + - content: echo "NOP" push_to_develop_or_master: description: |- This workflow is triggered for each new commit pushed to `develop` or `master` branch. - envs: - - DD_RUN_SR_UNIT_TESTS: '1' after_run: - _make_dependencies - - run_conditioned_workflows - run_e2e_s8s_upload - _deploy_artifacts - _notify_failure_on_slack @@ -45,13 +33,10 @@ workflows: - _notify_failure_on_slack tagged_commit: - description: |- - This workflow is triggered on pushing a new release tag. - envs: - - DD_RUN_SR_UNIT_TESTS: '1' - after_run: - - _make_dependencies - - run_conditioned_workflows + steps: + - script: + inputs: + - content: echo "NOP" _make_dependencies: description: |- @@ -92,59 +77,6 @@ workflows: - icon_url: 'https://avatars.githubusercontent.com/t/3555052?s=128&v=4' - webhook_url: '${SLACK_INCOMING_WEBHOOK_MOBILE_CI}' - run_conditioned_workflows: - steps: - - script: - title: Choose which workflows to run - inputs: - - content: |- - #!/usr/bin/env bash - - cd tools/ci && make - - # The `choose_workflows.py` inspects current ENV variables and Pull Request metadata (if running in PR) - # and decides on which from the workflows in `after_run` should be ran. Workflows are opted-in - # by modifying `DD_RUN_*` ENV variables with `envman` (ref.: https://github.com/bitrise-io/envman). - venv/bin/python3 choose_workflows.py - after_run: - - run_unit_tests - - run_unit_tests: - description: |- - Selectively runs: - - or Session Replay tests when when 'DD_RUN_SR_UNIT_TESTS' is '1' - steps: - - script: - title: Pull Session Replay snapshots - run_if: '{{enveq "DD_RUN_SR_UNIT_TESTS" "1"}}' - inputs: - - content: |- - #!/usr/bin/env zsh - set -e - make sr-pull-snapshots - - script: - title: Configure SRSnapshotTests project launch - run_if: '{{enveq "DD_RUN_SR_UNIT_TESTS" "1"}}' - inputs: - - content: | - #!/usr/bin/env zsh - - # The `SRSnapshotTests.xcworkspace` depends on `dd-sdk-ios/Package.swift` but requires the `dd-sdk-ios/TestUtilities` library, - # which is not defined statically in the root package. To add it dynamically, we leverage the `DD_TEST_UTILITIES_ENABLED` ENV - # variable respected by the main package. Here we export it so it is available in next CI steps: - envman add --key DD_TEST_UTILITIES_ENABLED --value '1' - - xcode-test: - title: Run snapshot tests for Session Replay - iOS Simulator - run_if: '{{enveq "DD_RUN_SR_UNIT_TESTS" "1"}}' - inputs: - - scheme: SRSnapshotTests - - destination: platform=iOS Simulator,name=iPhone 15,OS=17.5 - - should_build_before_test: 'no' - - is_clean_build: 'no' - - generate_code_coverage_files: 'yes' - - project_path: DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests.xcworkspace - - xcpretty_test_options: --color --report html --output "${BITRISE_DEPLOY_DIR}/DatadogSessionReplay-snapshot-tests.html" - create_dogfooding_pr: description: |- Creates PRs to repositories using `dd-sdk-ios`. From 736aeb0de3997b28486895c73e6a2175e6a6a9fd Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 9 Jul 2024 17:26:37 +0200 Subject: [PATCH 20/43] RUM-4079 Disintegrate `tools/ci/choose_workflows` automation it is no longer required for GitLab --- .github/PULL_REQUEST_TEMPLATE.md | 3 - tools/ci/Makefile | 9 -- tools/ci/choose_workflows.py | 191 ------------------------------- tools/ci/src/ci_context.py | 119 ------------------- tools/ci/src/utils.py | 43 ------- 5 files changed, 365 deletions(-) delete mode 100644 tools/ci/Makefile delete mode 100644 tools/ci/choose_workflows.py delete mode 100644 tools/ci/src/ci_context.py delete mode 100644 tools/ci/src/utils.py diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index 5a70d47bbb..af7892ad36 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -10,6 +10,3 @@ A brief description of implementation details of this PR. - [ ] Feature or bugfix MUST have appropriate tests (unit, integration) - [ ] Make sure each commit and the PR mention the Issue number or JIRA reference - [ ] Add CHANGELOG entry for user facing changes - -### Custom CI job configuration (optional) -- [ ] Run unit tests for Session Replay diff --git a/tools/ci/Makefile b/tools/ci/Makefile deleted file mode 100644 index 1172753065..0000000000 --- a/tools/ci/Makefile +++ /dev/null @@ -1,9 +0,0 @@ -.PHONY: all - -all: - @echo "⚙️ Ensuring that GitHub CLI is installed $(PWD)" - @brew list gh &>/dev/null || brew install gh -ifeq ($(wildcard venv),) - @echo "⚙️ Creating Python venv in $(PWD)" - python3 -m venv venv -endif diff --git a/tools/ci/choose_workflows.py b/tools/ci/choose_workflows.py deleted file mode 100644 index 9cd34d09be..0000000000 --- a/tools/ci/choose_workflows.py +++ /dev/null @@ -1,191 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import sys -import argparse -import traceback -from typing import Optional -from src.utils import export_env -from src.ci_context import get_ci_context, CIContext - - -def should_run_unit_tests(ctx: CIContext) -> bool: - print('⚙️ Resolving `unit tests` phase:') - return should_run_phase( - ctx=ctx, - trigger_env=context.trigger_env.DD_RUN_UNIT_TESTS, - build_env=context.build_env.DD_OVERRIDE_RUN_UNIT_TESTS, - pr_keyword='[x] Run unit tests', - pr_path_prefixes=[ - 'Datadog/Datadog.xcodeproj/', - 'TestUtilities/', - 'DatadogCore/', - 'DatadogRUM/', - 'DatadogCrashReporting/', - 'DatadogLogs/', - 'DatadogTrace/', - 'DatadogWebViewTracking/', - 'DatadogObjc/', - 'DatadogInternal/', - ], - pr_file_extensions=[] - ) - - -def should_run_sr_unit_tests(ctx: CIContext) -> bool: - print('⚙️ Resolving `unit tests` phase for Session Replay:') - return should_run_phase( - ctx=ctx, - trigger_env=context.trigger_env.DD_RUN_SR_UNIT_TESTS, - build_env=context.build_env.DD_OVERRIDE_RUN_SR_UNIT_TESTS, - pr_keyword='[x] Run unit tests for Session Replay', - pr_path_prefixes=[ - 'Datadog/Datadog.xcodeproj/', - 'DatadogSessionReplay/', - 'DatadogInternal/', - 'TestUtilities/', - ], - pr_file_extensions=[] - ) - - -def should_run_integration_tests(ctx: CIContext) -> bool: - print('⚙️ Resolving `integration tests` phase:') - return should_run_phase( - ctx=ctx, - trigger_env=context.trigger_env.DD_RUN_INTEGRATION_TESTS, - build_env=context.build_env.DD_OVERRIDE_RUN_INTEGRATION_TESTS, - pr_keyword='[x] Run integration tests', - pr_path_prefixes=[ - 'IntegrationTests/', - ], - pr_file_extensions=[] - ) - - -def should_run_smoke_tests(ctx: CIContext) -> bool: - print('⚙️ Resolving `smoke tests` phase:') - return should_run_phase( - ctx=ctx, - trigger_env=context.trigger_env.DD_RUN_SMOKE_TESTS, - build_env=context.build_env.DD_OVERRIDE_RUN_SMOKE_TESTS, - pr_keyword='[x] Run smoke tests', - pr_path_prefixes=[ - 'dependency-manager-tests/', - ], - pr_file_extensions=[ - '.podspec', - '.podspec.src', - 'Cartfile', - 'Cartfile.resolved', - 'Package.swift', - ] - ) - - -def should_run_tools_tests(ctx: CIContext) -> bool: - print('⚙️ Resolving `tools tests` phase:') - return should_run_phase( - ctx=ctx, - trigger_env=context.trigger_env.DD_RUN_TOOLS_TESTS, - build_env=context.build_env.DD_OVERRIDE_RUN_TOOLS_TESTS, - pr_keyword='[x] Run tests for `tools/`', - pr_path_prefixes=[ - 'instrumented-tests/', - 'tools/', - ], - pr_file_extensions=[] - ) - - -def should_run_phase( - ctx: CIContext, trigger_env: str, build_env: Optional[str], - pr_keyword: Optional[str], pr_path_prefixes: [str], pr_file_extensions: [str] -) -> bool: - """ - Resolves CI do determine if a phase should be ran as part of the workflow. - - :param ctx: CI context (ENVs and optional Pull Request information) - :param trigger_env: the name of a trigger-level ENV which decides on running this phase - :param build_env: the name of a build-level ENV which decides on running this phase - :param pr_keyword: the magic word to lookup in PR's description (or `None`) - if found, it will trigger this phase - :param pr_path_prefixes: the list of prefixes to match against PR's modified files (any match triggers this phase) - :param pr_file_extensions: the list of suffixes to match against PR's modified files (any match triggers this phase) - :return: True if a phase should be ran for given CI context - """ - # First, respect trigger ENV: - if trigger_env == '1': - print('→ opted-in by trigger ENV') - return True - - # Second, check build ENV: - if build_env == '1': - print('→ opted-in by build ENV') - return True - - # Last, infer from Pull Request (if running for PR): - if ctx.pull_request: - if pr_keyword and ctx.pull_request.description.contains(pr_keyword): - print(f'→ opted-in by matching keyword ("{pr_keyword}") in PR description ▶️') - return True - - if ctx.pull_request.modified_files.contains_paths(path_prefixes=pr_path_prefixes): - print(f'→ opted-in by matching one or more path prefixes ({pr_path_prefixes}) in PR files ▶️') - return True - - if ctx.pull_request.modified_files.contains_extensions(file_extensions=pr_file_extensions): - print(f'→ opted-in by matching one or more file extensions ({pr_file_extensions}) in PR files ▶️') - return True - - print(f'→ will be skipped ⏩') - return False - - -if __name__ == "__main__": - parser = argparse.ArgumentParser() - parser.add_argument( - "--dry-run", - action='store_true', - help="Run without exporting ENVs to `envman`." - ) - args = parser.parse_args() - - try: - dry_run = True if args.dry_run else False - - context = get_ci_context() - - print(f'🛠️️ CI Context:\n' - f'- trigger ENVs = {context.trigger_env}\n' - f'- build ENVs = {context.build_env}\n' - f'- PR files = {context.pull_request.modified_files.paths if context.pull_request else "(no PR)"}') - - if should_run_unit_tests(context): - export_env('DD_RUN_UNIT_TESTS', '1', dry_run=dry_run) - - if should_run_sr_unit_tests(context): - export_env('DD_RUN_SR_UNIT_TESTS', '1', dry_run=dry_run) - - if should_run_integration_tests(context): - export_env('DD_RUN_INTEGRATION_TESTS', '1', dry_run=dry_run) - - if should_run_smoke_tests(context): - export_env('DD_RUN_SMOKE_TESTS', '1', dry_run=dry_run) - - if should_run_tools_tests(context): - export_env('DD_RUN_TOOLS_TESTS', '1', dry_run=dry_run) - - except Exception as error: - print(f'❌ Failed with: {error}') - print('-' * 60) - traceback.print_exc(file=sys.stdout) - print('-' * 60) - sys.exit(1) - - sys.exit(0) diff --git a/tools/ci/src/ci_context.py b/tools/ci/src/ci_context.py deleted file mode 100644 index 3d16275459..0000000000 --- a/tools/ci/src/ci_context.py +++ /dev/null @@ -1,119 +0,0 @@ -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import json -import os -from typing import Optional -from dataclasses import dataclass -from src.utils import shell_output - - -@dataclass -class CIContext: - trigger_env: 'TriggerENVs' - build_env: 'BuildENVs' - pull_request: Optional['PullRequest'] - - -@dataclass -class TriggerENVs: - """ - ENVs defined for a trigger in `bitrise.yml`. - """ - DD_RUN_UNIT_TESTS: str - DD_RUN_SR_UNIT_TESTS: str - DD_RUN_INTEGRATION_TESTS: str - DD_RUN_SMOKE_TESTS: str - DD_RUN_TOOLS_TESTS: str - - -@dataclass -class BuildENVs: - """ - Optional ENVs passed from outside (when starting build manually). - """ - DD_OVERRIDE_RUN_UNIT_TESTS: Optional[str] - DD_OVERRIDE_RUN_SR_UNIT_TESTS: Optional[str] - DD_OVERRIDE_RUN_INTEGRATION_TESTS: Optional[str] - DD_OVERRIDE_RUN_SMOKE_TESTS: Optional[str] - DD_OVERRIDE_RUN_TOOLS_TESTS: Optional[str] - - -@dataclass -class PullRequest: - """ - Pull request context fetched from GH (available only for pull request builds). - """ - description: 'PullRequestDescription' - modified_files: 'PullRequestFiles' - - -@dataclass -class PullRequestDescription: - """ - Description of the PR. - """ - description: str - - def contains(self, word: str) -> bool: - return word in self.description - - -@dataclass -class PullRequestFiles: - """ - List of files modified by the PR. - """ - paths: [str] - - def contains_paths(self, path_prefixes: [str]) -> bool: - for path in self.paths: - for path_prefix in path_prefixes: - if path.startswith(path_prefix): - return True - return False - - def contains_extensions(self, file_extensions: [str]) -> bool: - for path in self.paths: - for file_extension in file_extensions: - if path.endswith(file_extension): - return True - return False - - -def get_ci_context() -> CIContext: - trigger_env = TriggerENVs( - DD_RUN_UNIT_TESTS=os.environ.get('DD_RUN_UNIT_TESTS') or "0", - DD_RUN_SR_UNIT_TESTS=os.environ.get('DD_RUN_SR_UNIT_TESTS') or "0", - DD_RUN_INTEGRATION_TESTS=os.environ.get('DD_RUN_INTEGRATION_TESTS') or "0", - DD_RUN_SMOKE_TESTS=os.environ.get('DD_RUN_SMOKE_TESTS') or "0", - DD_RUN_TOOLS_TESTS=os.environ.get('DD_RUN_TOOLS_TESTS') or "0" - ) - - build_env = BuildENVs( - DD_OVERRIDE_RUN_UNIT_TESTS=os.environ.get('DD_OVERRIDE_RUN_UNIT_TESTS'), - DD_OVERRIDE_RUN_SR_UNIT_TESTS=os.environ.get('DD_OVERRIDE_RUN_SR_UNIT_TESTS'), - DD_OVERRIDE_RUN_INTEGRATION_TESTS=os.environ.get('DD_OVERRIDE_RUN_INTEGRATION_TESTS'), - DD_OVERRIDE_RUN_SMOKE_TESTS=os.environ.get('DD_OVERRIDE_RUN_SMOKE_TESTS'), - DD_OVERRIDE_RUN_TOOLS_TESTS=os.environ.get('DD_OVERRIDE_RUN_TOOLS_TESTS') - ) - - if pull_request_id := os.environ.get('BITRISE_PULL_REQUEST'): - # Fetch PR details with GH CLI (ref.: https://cli.github.com/manual/gh_pr_view) - gh_cli_output = shell_output(f'gh pr view {pull_request_id} --json body,files') - pr_json = json.loads(gh_cli_output) - pull_request = PullRequest( - description=PullRequestDescription(pr_json['body']), - modified_files=PullRequestFiles(list(map(lambda file: file['path'], pr_json['files']))) - ) - else: - pull_request = None - - return CIContext( - trigger_env=trigger_env, - build_env=build_env, - pull_request=pull_request - ) diff --git a/tools/ci/src/utils.py b/tools/ci/src/utils.py deleted file mode 100644 index 0819e9e567..0000000000 --- a/tools/ci/src/utils.py +++ /dev/null @@ -1,43 +0,0 @@ -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import subprocess - - -def export_env(key: str, value: str, dry_run: bool): - """ - Exports ENV to other process using `envman` (ref.: https://github.com/bitrise-io/envman). - Once set, the ENV will be available to `envman` running in caller process. - """ - if not dry_run: - shell_output(f'envman add --key {key} --value "{value}"') - else: - print(f'[DRY-RUN] calling: `envman add --key {key} --value "{value}"`') - - -# Copied from `tools/nightly-unit-tests/src/utils.py` -# TODO: RUMM-1860 Share this code between both tools -def shell_output(command: str): - """ - Runs shell command and returns its output. - Fails on exit code != 0. - """ - process = subprocess.run( - args=[command], - capture_output=True, - shell=True, - text=True # capture STDOUT as text - ) - if process.returncode == 0: - return process.stdout - else: - raise Exception( - f''' - Command {command} exited with status code {process.returncode} - - STDOUT: {process.stdout if process.stdout != '' else '""'} - - STDERR: {process.stderr if process.stderr != '' else '""'} - ''' - ) From 69e25328f014d32c5ffcb19eebb1814d09514dbe Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Tue, 9 Jul 2024 17:44:22 +0200 Subject: [PATCH 21/43] RUM-4079 Refine `make sr-snapshot-tests-open` --- Makefile | 21 +++++++++---------- tools/sr-snapshot-test.sh | 44 +++++++++++++++++++++++++++++++-------- 2 files changed, 45 insertions(+), 20 deletions(-) diff --git a/Makefile b/Makefile index 195658659c..be577e6f4e 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ all: env-check repo-setup templates lint license-check \ test test-ios test-ios-all test-tvos test-tvos-all \ ui-test ui-test-all ui-test-podinstall \ - sr-snapshot-test sr-pull-snapshots sr-push-snapshots \ + sr-snapshot-test sr-snapshots-pull sr-snapshots-push sr-snapshot-tests-open \ tools-test \ smoke-test smoke-test-ios smoke-test-ios-all smoke-test-tvos smoke-test-tvos-all \ spm-build spm-build-ios spm-build-tvos spm-build-visionos spm-build-macos \ @@ -257,12 +257,6 @@ xcodeproj-session-replay: @cd DatadogSessionReplay/ && swift package generate-xcodeproj @echo "OK 👌" -open-sr-snapshot-tests: - @echo "⚙️ Opening SRSnapshotTests with DD_TEST_UTILITIES_ENABLED ..." - @pgrep -q Xcode && killall Xcode && echo "- Xcode killed" || echo "- Xcode not running" - @sleep 0.5 && echo "- launching" # Sleep, otherwise, if Xcode was running it often fails with "procNotFound: no eligible process with specified descriptor" - @open --env DD_TEST_UTILITIES_ENABLED ./DatadogSessionReplay/SRSnapshotTests/SRSnapshotTests.xcworkspace - templates: @$(ECHO_TITLE) "make templates" ./tools/xcode-templates/install-xcode-templates.sh @@ -299,13 +293,13 @@ sr-models-verify: @$(MAKE) models-verify PRODUCT="sr" # Pushes current SR snapshots to snapshots repo -sr-push-snapshots: - @$(ECHO_TITLE) "make sr-push-snapshots" +sr-snapshots-push: + @$(ECHO_TITLE) "make sr-snapshots-push" ./tools/sr-snapshot-test.sh --push # Pulls SR snapshots from snapshots repo -sr-pull-snapshots: - @$(ECHO_TITLE) "make sr-pull-snapshots" +sr-snapshots-pull: + @$(ECHO_TITLE) "make sr-snapshots-pull" ./tools/sr-snapshot-test.sh --pull # Run Session Replay snapshot tests @@ -318,6 +312,11 @@ sr-snapshot-test: ./tools/sr-snapshot-test.sh \ --test --os "$(OS)" --device "$(DEVICE)" --platform "$(PLATFORM)" --artifacts-path "$(ARTIFACTS_PATH)" +# Opens `SRSnapshotTests` project with passing required ENV variables +sr-snapshot-tests-open: + @$(ECHO_TITLE) "make sr-snapshot-tests-open" + ./tools/sr-snapshot-test.sh --open-project + # Generate api-surface files for Datadog and DatadogObjc. api-surface: @echo "Generating api-surface-swift" diff --git a/tools/sr-snapshot-test.sh b/tools/sr-snapshot-test.sh index a442010298..380b438776 100755 --- a/tools/sr-snapshot-test.sh +++ b/tools/sr-snapshot-test.sh @@ -1,18 +1,32 @@ #!/bin/zsh +# Usage: +# $ ./tools/sr-snapshot-test.sh -h +# Interacts with the SR Snapshot Tests project. + +# Options: +# --pull: Pulls snapshot images from the snapshots repository +# --push: Pushes snapshot images to the snapshots repository +# --open-project: Opens the SR Snapshot Tests project in Xcode with the required environment variables +# --test: Runs snapshot tests against snapshot images in the current repository +# --os: Sets the operating system version for --test, e.g., '17.5' +# --platform: Defines the type of simulator platform for --test, e.g., 'iOS Simulator' +# --device: Specifies the simulator device for --test, e.g., 'iPhone 15' +# --artifacts-path: Path to store the test bundle result + set -eo pipefail source ./tools/utils/argparse.sh source ./tools/utils/echo-color.sh -set_description "." -define_arg "pull" "false" "..." "store_true" -define_arg "push" "false" "..." "store_true" -define_arg "open-project" "false" "..." "store_true" -define_arg "test" "false" "..." "store_true" -define_arg "os" "" "Sets the operating system version for the tests, e.g. '17.5'" "string" "false" -define_arg "platform" "" "Defines the type of simulator platform for the tests, e.g. 'iOS Simulator'" "string" "false" -define_arg "device" "" "Specifies the simulator device for running tests, e.g. 'iPhone 15 Pro'" "string" "false" -define_arg "artifacts-path" "" "Path to artifacts for failed tests." "string" "false" +set_description "Interacts with the SR Snapshot Tests project." +define_arg "pull" "false" "Pulls snapshot images from the snapshots repository" "store_true" +define_arg "push" "false" "Pushes snapshot images to the snapshots repository" "store_true" +define_arg "open-project" "false" "Opens the SR Snapshot Tests project in Xcode with the required environment variables" "store_true" +define_arg "test" "false" "Runs snapshot tests against snapshot images in the current repository" "store_true" +define_arg "os" "" "Sets the operating system version for --test, e.g., '17.5'" "string" "false" +define_arg "platform" "" "Defines the type of simulator platform for --test, e.g., 'iOS Simulator'" "string" "false" +define_arg "device" "" "Specifies the simulator device for --test, e.g., 'iPhone 15'" "string" "false" +define_arg "artifacts-path" "" "Path to store the test bundle result" "string" "false" check_for_help "$@" parse_args "$@" @@ -59,6 +73,18 @@ test_snapshots() { xcodebuild -workspace "$TEST_WORKSPACE" -destination "$destination" -scheme "$TEST_SCHEME" -resultBundlePath "$TEST_ARTIFACTS_PATH/$TEST_SCHEME.xcresult" test | xcbeautify } +open_snapshot_tests_project() { + echo_info "Opening SRSnapshotTests with DD_TEST_UTILITIES_ENABLED ..." + pgrep -q Xcode && killall Xcode && echo_warn "- Xcode killed" || echo_succ "- Xcode not running" + sleep 0.5 && echo "- launching" # Sleep, otherwise, if Xcode was running it often fails with "procNotFound: no eligible process with specified descriptor" + open --env DD_TEST_UTILITIES_ENABLED "$TEST_WORKSPACE" +} + +if [ "$open_project" = "true" ]; then + open_snapshot_tests_project + exit 0 +fi + echo_info "Using" echo_info "▸ SNAPSHOTS_CLI_PATH = '$SNAPSHOTS_CLI_PATH'" echo_info "▸ SNAPSHOTS_PATH = '$SNAPSHOTS_DIR'" From e0bcff391530d7ae352464df92c35afb060964fc Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 10 Jul 2024 11:41:05 +0200 Subject: [PATCH 22/43] remove tvOS target --- Datadog/Datadog.xcodeproj/project.pbxproj | 2 -- 1 file changed, 2 deletions(-) diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index 639d1ac330..372dae86ba 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -54,7 +54,6 @@ 3C5D691F2B76825500C4E07E /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; }; 3C5D69222B76826000C4E07E /* OpenTelemetryApi.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3C1F88222B767CE200821579 /* OpenTelemetryApi.xcframework */; }; 3C62C3612C3E852F00C7E336 /* MultiSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C62C3602C3E852F00C7E336 /* MultiSelector.swift */; }; - 3C62C3622C3E852F00C7E336 /* MultiSelector.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C62C3602C3E852F00C7E336 /* MultiSelector.swift */; }; 3C6C7FE72B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; 3C6C7FE82B459AAA006F5CBC /* OTelSpan.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE02B459AAA006F5CBC /* OTelSpan.swift */; }; 3C6C7FE92B459AAA006F5CBC /* OTelSpanBuilder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C6C7FE12B459AAA006F5CBC /* OTelSpanBuilder.swift */; }; @@ -8825,7 +8824,6 @@ files = ( 1434A4672B7F8D880072E3BB /* DebugOTelTracingViewController.swift in Sources */, D2F44FC3299BD5600074B0D9 /* UIViewController+KeyboardControlling.swift in Sources */, - 3C62C3622C3E852F00C7E336 /* MultiSelector.swift in Sources */, D240680827CE6C9E00C04F44 /* ConsoleOutputInterceptor.swift in Sources */, D240681E27CE6C9E00C04F44 /* ExampleAppDelegate.swift in Sources */, D240682B27CE6C9E00C04F44 /* UIButton+Disabling.swift in Sources */, From 10c4f6111ea2bacb7669a47219f75f6cce653e10 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 11 Jul 2024 17:30:09 +0200 Subject: [PATCH 23/43] RUM-4079 Fix make command name --- .gitlab-ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4c95390e1c..8df66ad7b3 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -147,7 +147,7 @@ SR Snapshot Tests: script: - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --os "$OS" --ssh # temporary, waiting for AMI - make clean repo-setup ENV=ci - - make sr-pull-snapshots sr-snapshot-test OS="$OS" PLATFORM="$PLATFORM" DEVICE="$DEVICE" ARTIFACTS_PATH="$ARTIFACTS_PATH" + - make sr-snapshots-pull sr-snapshot-test OS="$OS" PLATFORM="$PLATFORM" DEVICE="$DEVICE" ARTIFACTS_PATH="$ARTIFACTS_PATH" Tools Tests: stage: test From 4d67a530cb6ad06b210c4fe1b06e816686a0a13f Mon Sep 17 00:00:00 2001 From: Maxime Epain Date: Thu, 11 Jul 2024 18:28:37 +0200 Subject: [PATCH 24/43] Remove DeviceProtocol --- Datadog/Datadog.xcodeproj/project.pbxproj | 6 --- .../Sources/Context/DeviceInfo.swift | 26 ++++++++++++- .../Sources/Context/DeviceProtocol.swift | 38 ------------------- 3 files changed, 24 insertions(+), 46 deletions(-) delete mode 100644 DatadogInternal/Sources/Context/DeviceProtocol.swift diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index b65fd3a564..3c84945753 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -1680,8 +1680,6 @@ E1C853142AA9B9A300C74BCF /* TelemetryMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C853132AA9B9A300C74BCF /* TelemetryMocks.swift */; }; E1C853152AA9B9A300C74BCF /* TelemetryMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1C853132AA9B9A300C74BCF /* TelemetryMocks.swift */; }; E1D5AEA724B4D45B007F194B /* Versioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = E1D5AEA624B4D45A007F194B /* Versioning.swift */; }; - E2AA55E42C32C6AF002FEF28 /* DeviceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E32C32C6AF002FEF28 /* DeviceProtocol.swift */; }; - E2AA55E52C32C6AF002FEF28 /* DeviceProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E32C32C6AF002FEF28 /* DeviceProtocol.swift */; }; E2AA55E72C32C6D9002FEF28 /* ApplicationNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */; }; E2AA55E82C32C6D9002FEF28 /* ApplicationNotifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */; }; E2AA55EA2C32C76A002FEF28 /* WatchKitExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */; }; @@ -3033,7 +3031,6 @@ E1D202E924C065CF00D1AF3A /* ActiveSpansPool.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSpansPool.swift; sourceTree = ""; }; E1D203FB24C1884500D1AF3A /* ActiveSpansPoolTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSpansPoolTests.swift; sourceTree = ""; }; E1D5AEA624B4D45A007F194B /* Versioning.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Versioning.swift; sourceTree = ""; }; - E2AA55E32C32C6AF002FEF28 /* DeviceProtocol.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DeviceProtocol.swift; sourceTree = ""; }; E2AA55E62C32C6D9002FEF28 /* ApplicationNotifications.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ApplicationNotifications.swift; sourceTree = ""; }; E2AA55E92C32C76A002FEF28 /* WatchKitExtensions.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WatchKitExtensions.swift; sourceTree = ""; }; F637AED12697404200516F32 /* UIKitRUMUserActionsPredicate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIKitRUMUserActionsPredicate.swift; sourceTree = ""; }; @@ -5827,7 +5824,6 @@ D23039BA298D5235001A1FA3 /* DatadogContext.swift */, D23039BB298D5235001A1FA3 /* TrackingConsent.swift */, D23039BC298D5235001A1FA3 /* DeviceInfo.swift */, - E2AA55E32C32C6AF002FEF28 /* DeviceProtocol.swift */, D23039BE298D5235001A1FA3 /* LaunchTime.swift */, D2F8235229915E12003C7E99 /* DatadogSite.swift */, 6174D6122BFDF16C00EC7469 /* BundleType.swift */, @@ -8597,7 +8593,6 @@ D2160CC929C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift in Sources */, D2EBEE2929BA160F00B15732 /* HTTPHeadersWriter.swift in Sources */, D2432CF929EDB22C00D93657 /* Flushable.swift in Sources */, - E2AA55E42C32C6AF002FEF28 /* DeviceProtocol.swift in Sources */, D23039F7298D5236001A1FA3 /* AttributesSanitizer.swift in Sources */, D23039EB298D5236001A1FA3 /* DatadogFeature.swift in Sources */, D2BEEDBA2B33638F0065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */, @@ -9566,7 +9561,6 @@ D2160CCA29C0DED100FAA9A5 /* DatadogURLSessionDelegate.swift in Sources */, D2EBEE3729BA161100B15732 /* HTTPHeadersWriter.swift in Sources */, D2432CFA29EDB22C00D93657 /* Flushable.swift in Sources */, - E2AA55E52C32C6AF002FEF28 /* DeviceProtocol.swift in Sources */, D2DA235D298D57AA00C6C7E6 /* AttributesSanitizer.swift in Sources */, D2DA235E298D57AA00C6C7E6 /* DatadogFeature.swift in Sources */, D2BEEDBB2B3363900065F3AC /* NetworkInstrumentationSwizzler.swift in Sources */, diff --git a/DatadogInternal/Sources/Context/DeviceInfo.swift b/DatadogInternal/Sources/Context/DeviceInfo.swift index 00b3b832a7..9c63c2463f 100644 --- a/DatadogInternal/Sources/Context/DeviceInfo.swift +++ b/DatadogInternal/Sources/Context/DeviceInfo.swift @@ -79,10 +79,10 @@ extension DeviceInfo { /// /// - Parameters: /// - processInfo: The current process information. - /// - device: The `DeviceProtocol` description. + /// - device: The device description. public init( processInfo: ProcessInfo = .processInfo, - device: DeviceProtocol = DeviceFactory.current, + device: _UIDevice = .dd.current, sysctl: SysctlProviding = Sysctl() ) { var architecture = "unknown" @@ -168,3 +168,25 @@ extension DeviceInfo { } } #endif + +#if canImport(WatchKit) +import WatchKit + +public typealias _UIDevice = WKInterfaceDevice + +extension _UIDevice: DatadogExtended {} +extension DatadogExtension where ExtendedType == _UIDevice { + /// Returns the shared device object. + public static var current: ExtendedType { .current() } +} +#elseif canImport(UIKit) +import UIKit + +public typealias _UIDevice = UIDevice + +extension _UIDevice: DatadogExtended {} +extension DatadogExtension where ExtendedType == _UIDevice { + /// Returns the shared device object. + public static var current: ExtendedType { .current } +} +#endif diff --git a/DatadogInternal/Sources/Context/DeviceProtocol.swift b/DatadogInternal/Sources/Context/DeviceProtocol.swift deleted file mode 100644 index a6b71a695a..0000000000 --- a/DatadogInternal/Sources/Context/DeviceProtocol.swift +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. - * This product includes software developed at Datadog (https://www.datadoghq.com/). - * Copyright 2019-Present Datadog, Inc. - */ - -import Foundation - -/// Abstraction protocol to get device information on the current platform -public protocol DeviceProtocol { - var model: String { get } - var systemName: String { get } - var systemVersion: String { get } - var identifierForVendor: UUID? { get } -} - -#if canImport(UIKit) -import UIKit - -#if canImport(WatchKit) -import WatchKit - -extension WKInterfaceDevice: DeviceProtocol {} -#else -extension UIDevice: DeviceProtocol {} -#endif - -public enum DeviceFactory { - /// Get the current device - public static var current: DeviceProtocol { - #if canImport(WatchKit) - WKInterfaceDevice.current() - #else - UIDevice.current - #endif - } -} -#endif From 6f1b3c66766a0529731e9461352501b5f065e9b9 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 3 Jul 2024 16:33:28 +0200 Subject: [PATCH 25/43] RUM-5084 feat: add ability to clear data in feature data storage --- CHANGELOG.md | 2 ++ .../Core/DataStore/FeatureDataStore.swift | 16 ++++++++++++++-- DatadogCore/Sources/Core/DatadogCore.swift | 13 +++++++++---- .../Sources/Core/Storage/Directories.swift | 13 ++++++++++++- .../Sources/Core/Storage/Files/File.swift | 2 +- DatadogCore/Tests/Datadog/DatadogTests.swift | 18 +++++++++++++++--- .../Sources/DataStore/DataStore.swift | 8 ++++++++ TestUtilities/Mocks/DataStoreMock.swift | 10 +++++++--- 8 files changed, 68 insertions(+), 14 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8cdde085c8..d24b8927d2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,7 @@ # Unreleased - [FEATURE] Enable DatadogCore, DatadogLogs and DatadogTrace to compile on watchOS platform. See [#1918][] (Thanks [@jfiser-paylocity][]) [#1946][] +- [IMPROVEMENT] Ability to clear feature data storage using `clearAllData` API. See [#1940][] # 2.14.1 / 09-07-2024 @@ -721,6 +722,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1938]: https://github.com/DataDog/dd-sdk-ios/pull/1938 [#1947]: https://github.com/DataDog/dd-sdk-ios/pull/1947 [#1948]: https://github.com/DataDog/dd-sdk-ios/pull/1948 +[#1940]: https://github.com/DataDog/dd-sdk-ios/pull/1940 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu diff --git a/DatadogCore/Sources/Core/DataStore/FeatureDataStore.swift b/DatadogCore/Sources/Core/DataStore/FeatureDataStore.swift index c5aa6d348f..750dda7166 100644 --- a/DatadogCore/Sources/Core/DataStore/FeatureDataStore.swift +++ b/DatadogCore/Sources/Core/DataStore/FeatureDataStore.swift @@ -9,7 +9,7 @@ import DatadogInternal /// A concrete implementation of the `DataStore` protocol using file storage. internal final class FeatureDataStore: DataStore { - private enum Constants { + enum Constants { /// The version of this data store implementation. /// If a breaking change is introduced to the format of managed files, the version must be upgraded and old data should be deleted. static let dataStoreVersion = 1 @@ -35,7 +35,7 @@ internal final class FeatureDataStore: DataStore { ) { self.feature = feature self.coreDirectory = directory - self.directoryPath = "\(Constants.dataStoreVersion)/" + feature + self.directoryPath = coreDirectory.getDataStorePath(forFeatureNamed: feature) self.queue = queue self.telemetry = telemetry } @@ -87,6 +87,18 @@ internal final class FeatureDataStore: DataStore { } } + func clearAllData() { + queue.async { + do { + let directory = try self.coreDirectory.coreDirectory.subdirectory(path: self.directoryPath) + try directory.deleteAllFiles() + } catch let error { + DD.logger.error("[Data Store] Error on clearing all data for `\(self.feature)`", error: error) + self.telemetry.error("[Data Store] Error on clearing all data for `\(self.feature)`", error: DDError(error: error)) + } + } + } + // MARK: - Persistence private func write(data: Data, forKey key: String, version: DataStoreKeyVersion) throws { diff --git a/DatadogCore/Sources/Core/DatadogCore.swift b/DatadogCore/Sources/Core/DatadogCore.swift index 9f8352545f..bbae88954f 100644 --- a/DatadogCore/Sources/Core/DatadogCore.swift +++ b/DatadogCore/Sources/Core/DatadogCore.swift @@ -53,10 +53,7 @@ internal final class DatadogCore { /// Registry for Features. @ReadWriteLock - private(set) var stores: [String: ( - storage: FeatureStorage, - upload: FeatureUpload - )] = [:] + private(set) var stores: [String: (storage: FeatureStorage, upload: FeatureUpload)] = [:] /// Registry for Features. @ReadWriteLock @@ -171,6 +168,7 @@ internal final class DatadogCore { /// Clears all data that has not already yet been uploaded Datadog servers. func clearAllData() { allStorages.forEach { $0.clearAllData() } + allDataStores.forEach { $0.clearAllData() } } /// Adds a message receiver to the bus. @@ -197,6 +195,13 @@ internal final class DatadogCore { stores.values.map { $0.upload } } + private var allDataStores: [DataStore] { + features.values.compactMap { feature in + let featureType = type(of: feature) as DatadogFeature.Type + return scope(for: featureType).dataStore + } + } + /// Awaits completion of all asynchronous operations, forces uploads (without retrying) and deinitializes /// this instance of the SDK. It **blocks the caller thread**. /// diff --git a/DatadogCore/Sources/Core/Storage/Directories.swift b/DatadogCore/Sources/Core/Storage/Directories.swift index 22282515cb..6954f189c4 100644 --- a/DatadogCore/Sources/Core/Storage/Directories.swift +++ b/DatadogCore/Sources/Core/Storage/Directories.swift @@ -35,11 +35,22 @@ internal struct CoreDirectory { authorized: try coreDirectory.createSubdirectory(path: "\(name)/v2") ) } + + /// Obtains the path to the data store for given Feature. + /// + /// Note: `FeatureDataStore` directory is created on-demand which may happen before `FeatureDirectories` are created. + /// Hence, this method only returns the path and let the caller decide if the directory should be created. + /// + /// - Parameter name: The given Feature name. + /// - Returns: The path to the data store for given Feature. + func getDataStorePath(forFeatureNamed name: String) -> String { + return "\(FeatureDataStore.Constants.dataStoreVersion)/" + name + } } internal extension CoreDirectory { /// Creates the core directory. - /// + /// /// - Parameters: /// - osDirectory: the root OS directory (`/Library/Caches`) to create core directory inside. /// - instanceName: The core instance name. diff --git a/DatadogCore/Sources/Core/Storage/Files/File.swift b/DatadogCore/Sources/Core/Storage/Files/File.swift index e3498d498e..e4ad03dfda 100644 --- a/DatadogCore/Sources/Core/Storage/Files/File.swift +++ b/DatadogCore/Sources/Core/Storage/Files/File.swift @@ -48,7 +48,7 @@ private enum FileError: Error { /// An immutable `struct` designed to provide optimized and thread safe interface for file manipulation. /// It doesn't own the file, which means the file presence is not guaranteed - the file can be deleted by OS at any time (e.g. due to memory pressure). -internal struct File: WritableFile, ReadableFile, FileProtocol { +internal struct File: WritableFile, ReadableFile, FileProtocol, Equatable { let url: URL let name: String diff --git a/DatadogCore/Tests/Datadog/DatadogTests.swift b/DatadogCore/Tests/Datadog/DatadogTests.swift index d1eb48fb65..221d3f0232 100644 --- a/DatadogCore/Tests/Datadog/DatadogTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogTests.swift @@ -336,7 +336,16 @@ class DatadogTests: XCTestCase { try core.directory.getFeatureDirectories(forFeatureNamed: "tracing"), ] - let allDirectories: [Directory] = featureDirectories.flatMap { [$0.authorized, $0.unauthorized] } + let scope = core.scope(for: TraceFeature.self) + scope.dataStore.setValue("foo".data(using: .utf8)!, forKey: "bar") + + // Wait for async clear completion in all features: + core.readWriteQueue.sync {} + let tracingDataStoreDir = try core.directory.coreDirectory.subdirectory(path: core.directory.getDataStorePath(forFeatureNamed: "tracing")) + XCTAssertTrue(tracingDataStoreDir.hasFile(named: "bar")) + + var allDirectories: [Directory] = featureDirectories.flatMap { [$0.authorized, $0.unauthorized] } + allDirectories.append(.init(url: tracingDataStoreDir.url)) try allDirectories.forEach { directory in _ = try directory.createFile(named: .mockRandom()) } // When @@ -346,8 +355,11 @@ class DatadogTests: XCTestCase { core.readWriteQueue.sync {} // Then - let newNumberOfFiles = try allDirectories.reduce(0, { acc, nextDirectory in return try acc + nextDirectory.files().count }) - XCTAssertEqual(newNumberOfFiles, 0, "All files must be removed") + let files: [File] = allDirectories.reduce([], { acc, nextDirectory in + let next = try? nextDirectory.files() + return acc + (next ?? []) + }) + XCTAssertEqual(files, [], "All files must be removed") Datadog.flushAndDeinitialize() } diff --git a/DatadogInternal/Sources/DataStore/DataStore.swift b/DatadogInternal/Sources/DataStore/DataStore.swift index 4d9b170d9f..e437ac639b 100644 --- a/DatadogInternal/Sources/DataStore/DataStore.swift +++ b/DatadogInternal/Sources/DataStore/DataStore.swift @@ -61,6 +61,12 @@ public protocol DataStore { /// /// - Parameter key: The unique identifier for the value to be deleted. Must be a valid file name, as it will be persisted in files. func removeValue(forKey key: String) + + /// Clears all data that has not already yet been uploaded Datadog servers. + /// + /// Note: This may impact the SDK's ability to detect App Hangs and Watchdog Terminations + /// or other features that rely on data persisted in the data store. + func clearAllData() } public extension DataStore { @@ -83,4 +89,6 @@ public struct NOPDataStore: DataStore { public func value(forKey key: String, callback: @escaping (DataStoreValueResult) -> Void) {} /// no-op public func removeValue(forKey key: String) {} + /// no-op + public func clearAllData() {} } diff --git a/TestUtilities/Mocks/DataStoreMock.swift b/TestUtilities/Mocks/DataStoreMock.swift index 212b9d51ea..77a2fd6243 100644 --- a/TestUtilities/Mocks/DataStoreMock.swift +++ b/TestUtilities/Mocks/DataStoreMock.swift @@ -18,15 +18,19 @@ public class DataStoreMock: DataStore { public func setValue(_ value: Data, forKey key: String, version: DataStoreKeyVersion) { storage[key] = .value(value, version) } - + public func value(forKey key: String, callback: @escaping (DataStoreValueResult) -> Void) { callback(storage[key] ?? .noValue) } - + public func removeValue(forKey key: String) { storage[key] = nil } - + + public func clearAllData() { + storage.removeAll() + } + // MARK: - Side Effects Observation public func value(forKey key: String) -> DataStoreValueResult? { From e2aa865738239b807fe99d46c34f0928b5fca873 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 11 Jul 2024 14:51:07 +0200 Subject: [PATCH 26/43] RUM-4079 chore: Migrate E2E Tests app workflow to GitLab --- .gitignore | 2 +- .gitlab-ci.yml | 23 ++++++ E2ETests/Makefile | 58 +++++++++---- Makefile | 11 ++- bitrise.yml | 33 -------- tools/code-sign.sh | 79 ------------------ tools/e2e-build-upload.sh | 134 +++++++++++++++++++++++++++++++ tools/env-check.sh | 10 +++ tools/release/publish-github.sh | 5 +- tools/release/publish-podspec.sh | 3 + tools/runner-setup.sh | 15 ++++ tools/secrets/config.sh | 14 ++++ tools/secrets/get-secret.sh | 2 +- tools/secrets/set-secret.sh | 20 ++++- tools/utils/common.mk | 1 + tools/utils/current-git.sh | 13 +++ 16 files changed, 285 insertions(+), 138 deletions(-) delete mode 100755 tools/code-sign.sh create mode 100755 tools/e2e-build-upload.sh diff --git a/.gitignore b/.gitignore index 45e09e1325..c30e539da4 100644 --- a/.gitignore +++ b/.gitignore @@ -7,8 +7,8 @@ Carthage/Build Carthage/Checkouts xcuserdata/ - *.local.xcconfig +E2ETests/code-signing # Ignore files for Python tools: .idea diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 011f2e0aa4..c025e1736c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -4,6 +4,7 @@ stages: - test - ui-test - smoke-test + - e2e-test - release-build - release-publish @@ -61,6 +62,7 @@ ENV check: - !reference [.test-pipeline-job, rules] - !reference [.release-pipeline-job, rules] script: + - ./tools/runner-setup.sh --datadog-ci # temporary, waiting for AMI - make env-check # ┌──────────────────────────┐ @@ -245,6 +247,27 @@ Smoke Tests (watchOS): - make clean repo-setup ENV=ci - make spm-build-watchos +# ┌──────────────────────┐ +# │ E2E Test app upload: │ +# └──────────────────────┘ + +E2E Test (upload to s8s): + stage: e2e-test + rules: + - if: '$CI_COMMIT_BRANCH == $DEVELOP_BRANCH' + variables: + XCODE: "15.3.0" + OS: "17.4" + artifacts: + paths: + - artifacts + expire_in: 2 weeks + script: + - ./tools/runner-setup.sh --xcode "$XCODE" --iOS --os "$OS" --datadog-ci # temporary, waiting for AMI + - make clean + - export DRY_RUN=${DRY_RUN:-0} # default to 0 if not specified + - make e2e-build-upload ARTIFACTS_PATH="artifacts/e2e" + # ┌──────────────┐ # │ SDK release: │ # └──────────────┘ diff --git a/E2ETests/Makefile b/E2ETests/Makefile index 43af3e81f3..fac3626242 100644 --- a/E2ETests/Makefile +++ b/E2ETests/Makefile @@ -1,30 +1,58 @@ -all: dependencies archive upload +.PHONY: clean archive export upload -dependencies: - @echo "⚙️ Installing datadog-ci..." - @npm install -g @datadog/datadog-ci +REPO_ROOT := ../ +include ../tools/utils/common.mk + +BUILD_DIR := .build +ARCHIVE_PATH := $(BUILD_DIR)/Runner.xcarchive +IPA_PATH := $(ARTIFACTS_PATH)/Runner.ipa + +clean: + @$(ECHO_SUBTITLE2) "make clean" + rm -rf "$(BUILD_DIR)" +ifdef ARTIFACTS_PATH + rm -rf "$(IPA_PATH)" +endif archive: - xcrun agvtool new-version "$(shell git rev-parse --short HEAD)" - - set -o pipefail && xcodebuild \ + @:$(eval VERSION ?= $(CURRENT_GIT_COMMIT_SHORT)) + @$(ECHO_SUBTITLE2) "make archive VERSION='$(VERSION)'" + @xcrun agvtool new-version "$(VERSION)" + set -eo pipefail; \ + xcodebuild \ -project E2ETests.xcodeproj \ -scheme Runner \ -sdk iphoneos \ -configuration Synthetics \ -destination generic/platform=iOS \ - -archivePath .build/Runner.xcarchive \ - archive | xcbeautify + -archivePath $(ARCHIVE_PATH) \ + archive | xcbeautify + git restore E2ETests.xcodeproj/project.pbxproj + @$(ECHO_SUCCESS) "Archive ready in '$(ARCHIVE_PATH)'" - set -o pipefail && xcodebuild -exportArchive \ - -archivePath .build/Runner.xcarchive \ +export: + @$(call require_param,ARTIFACTS_PATH) + @:$(eval VERSION ?= $(CURRENT_GIT_COMMIT_SHORT)) + @$(ECHO_SUBTITLE2) "make export VERSION='$(VERSION)' ARTIFACTS_PATH='$(ARTIFACTS_PATH)'" + set -o pipefaill; \ + xcodebuild -exportArchive \ + -archivePath $(ARCHIVE_PATH) \ -exportOptionsPlist exportOptions.plist \ - -exportPath .build \ - | xcbeautify + -exportPath $(BUILD_DIR) \ + | xcbeautify + mkdir -p "$(ARTIFACTS_PATH)" + cp -v "$(BUILD_DIR)/Runner.ipa" "$(IPA_PATH)" + @$(ECHO_SUCCESS) "IPA exported to '$(IPA_PATH)'" upload: + @$(call require_param,ARTIFACTS_PATH) + @$(call require_param,DATADOG_API_KEY) + @$(call require_param,DATADOG_APP_KEY) + @$(call require_param,S8S_APPLICATION_ID) + @:$(eval VERSION ?= $(CURRENT_GIT_COMMIT_SHORT)) + @$(ECHO_SUBTITLE2) "make upload VERSION='$(VERSION)' ARTIFACTS_PATH='$(ARTIFACTS_PATH)'" datadog-ci synthetics upload-application \ - --mobileApp ".build/Runner.ipa" \ + --mobileApp "$(IPA_PATH)" \ --mobileApplicationId "${S8S_APPLICATION_ID}" \ - --versionName "$(shell agvtool vers -terse)" \ + --versionName "$(VERSION)" \ --latest diff --git a/Makefile b/Makefile index 322c9601c3..e9f782dff4 100644 --- a/Makefile +++ b/Makefile @@ -7,6 +7,7 @@ all: env-check repo-setup templates tools-test \ smoke-test smoke-test-ios smoke-test-ios-all smoke-test-tvos smoke-test-tvos-all \ spm-build spm-build-ios spm-build-tvos spm-build-visionos spm-build-macos spm-build-watchos \ + e2e-build-upload \ models-generate rum-models-generate sr-models-generate models-verify rum-models-verify sr-models-verify \ release-build release-validate release-publish-github \ release-publish-podspec release-publish-internal-podspecs release-publish-dependent-podspecs release-publish-legacy-podspecs \ @@ -259,6 +260,13 @@ spm-build-macos: @$(MAKE) spm-build DESTINATION="platform=macOS" SCHEME="DatadogTrace" @$(MAKE) spm-build DESTINATION="platform=macOS" SCHEME="DatadogCrashReporting" +# Builds a new version of the E2E app and publishes it to synthetics. +e2e-build-upload: + @$(call require_param,ARTIFACTS_PATH) + @:$(eval DRY_RUN ?= 1) + @$(ECHO_TITLE) "make e2e-build-upload ARTIFACTS_PATH='$(ARTIFACTS_PATH)' DRY_RUN='$(DRY_RUN)'" + DRY_RUN=$(DRY_RUN) ./tools/e2e-build-upload.sh --artifacts-path "$(ARTIFACTS_PATH)" + xcodeproj-session-replay: @echo "⚙️ Generating 'DatadogSessionReplay.xcodeproj'..." @cd DatadogSessionReplay/ && swift package generate-xcodeproj @@ -430,6 +438,3 @@ bump: git add . ; \ git commit -m "Bumped version to $$version"; \ echo Bumped version to $$version - -e2e-upload: - ./tools/code-sign.sh -- $(MAKE) -C E2ETests diff --git a/bitrise.yml b/bitrise.yml index 113da244e4..7af92f18c2 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -17,9 +17,7 @@ workflows: This workflow is triggered for each new commit pushed to `develop` or `master` branch. after_run: - _make_dependencies - - run_e2e_s8s_upload - _deploy_artifacts - - _notify_failure_on_slack push_to_dogfooding: after_run: @@ -109,34 +107,3 @@ workflows: - destination: platform=iOS Simulator,name=iPhone 11,OS=latest - project_path: Datadog.xcworkspace - xcpretty_test_options: --color --report html --output "${BITRISE_DEPLOY_DIR}/E2E-instrumentation-tests.html" - - run_e2e_s8s_upload: - description: |- - Upload E2E application to Synthetics. - steps: - - script: - title: Upload E2E application to Synthetics. - run_if: '{{enveq "BITRISE_GIT_BRANCH" "develop"}}' - inputs: - - content: |- - #!/usr/bin/env bash - set -e - - # prepare certificate - export P12_PATH=e2e_cert.p12 - export P12_PASSWORD=$E2E_CERTIFICATE_P12_PASSWORD - echo $E2E_CERTIFICATE_P12_BASE64 | base64 --decode -o $P12_PATH - - # prepare provisioning profile - export PP_PATH=e2e.mobileprovision - echo $E2E_PROVISIONING_PROFILE_BASE64 | base64 --decode -o $PP_PATH - - # prepare xcconfig - echo $E2E_XCCONFIG_BASE64 | base64 --decode -o E2ETests/xcconfigs/E2E.local.xcconfig - - # prepare for synthetics upload - export DATADOG_API_KEY=$E2E_S8S_API_KEY - export DATADOG_APP_KEY=$E2E_S8S_APPLICATION_KEY - export S8S_APPLICATION_ID=$E2E_S8S_APPLICATION_ID - - make e2e-upload diff --git a/tools/code-sign.sh b/tools/code-sign.sh deleted file mode 100755 index 4b7b2aa8b4..0000000000 --- a/tools/code-sign.sh +++ /dev/null @@ -1,79 +0,0 @@ -#!/bin/bash - -function usage() { - cat << EOF -OVERVIEW: Install Apple certificate and provisioning profile to build and sign. - -EXAMPLE: $(basename "${BASH_SOURCE[0]}") -- make export - -USAGE: $(basename "${BASH_SOURCE[0]}") [--p12 ] [--p12-password ] [--provisioning-profile] -- - -OPTIONS: - --h, --help Print this help and exit. ---p12 Path to Apple signing 'p12' certificate. env P12_PATH ---p12-password The password for yotheur Apple signing certificate. env P12_PASSWORD ---provisioning-profile Path to Apple provisioning profile. env PP_PATH - -EOF - exit -} - -# read cmd arguments -while :; do - case $1 in - --p12) P12_PATH=$2 - shift - ;; - --p12-password) P12_PASSWORD=$2 - shift - ;; - --provisioning-profile) PP_PATH=$2 - shift - ;; - -h|--help) usage - shift - ;; - --) shift - CMD=$@ - break - ;; - *) break - esac - shift -done - -if [ -z "$P12_PATH" ] || [ -z "$P12_PASSWORD" ] || [ -z "$PP_PATH" ] || [ -z "$CMD" ]; then usage; fi - -# Ensure we do not leak any secrets -set +x -e - -KEYCHAIN=datadog.keychain -KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" -PROFILE=datadog.mobileprovision - -cleanup() { - rm -f ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE - security delete-keychain $KEYCHAIN -} - -# clean up keychain and provisioning profile on exit -trap cleanup EXIT - -# create temporary keychain -security delete-keychain $KEYCHAIN || : -security create-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN -security set-keychain-settings -lut 21600 $KEYCHAIN -security unlock-keychain -p $KEYCHAIN_PASSWORD $KEYCHAIN - -# import certificate to keychain -security import $P12_PATH -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k $KEYCHAIN -security list-keychain -d user -s $KEYCHAIN "login.keychain" "System.keychain" -security set-key-partition-list -S apple-tool:,apple: -s -k $KEYCHAIN_PASSWORD $KEYCHAIN >/dev/null 2>&1 - -# apply provisioning profile -mkdir -p ~/Library/MobileDevice/Provisioning\ Profiles -cp $PP_PATH ~/Library/MobileDevice/Provisioning\ Profiles/$PROFILE - -# run command with certificate and provisioning profile available -exec $CMD diff --git a/tools/e2e-build-upload.sh b/tools/e2e-build-upload.sh new file mode 100755 index 0000000000..2064e39ff8 --- /dev/null +++ b/tools/e2e-build-upload.sh @@ -0,0 +1,134 @@ +#!/bin/zsh + +# Usage: +# $ ./tools/e2e-build-upload.sh -h +# Publishes IPA of a new version of the E2E app to synthetics. + +# Options: +# --artifacts-path: Path where the IPA artifact will be exported. + +# ENVs: +# - DRY_RUN: Set to '1' to do everything except uploading the IPA to synthetics. + +set +x +set -eo pipefail +source ./tools/utils/argparse.sh +source ./tools/utils/echo-color.sh +source ./tools/secrets/get-secret.sh + +set_description "Publishes IPA a new version of the E2E app to synthetics." +define_arg "artifacts-path" "" "Path where the IPA artifact will be exported." "string" "true" + +check_for_help "$@" +parse_args "$@" + +KEYCHAIN=datadog.e2e.keychain +KEYCHAIN_PASSWORD="$(openssl rand -base64 32)" +PROFILE=datadog.e2e.mobileprovision + +E2E_DIR="E2ETests" +E2E_XCCONFIG_PATH="$E2E_DIR/xcconfigs/E2E.local.xcconfig" +E2E_CODESIGN_DIR="$E2E_DIR/code-signing" +P12_PATH="$E2E_CODESIGN_DIR/e2e_cert.p12" +PP_PATH="$E2E_CODESIGN_DIR/e2e.mobileprovision" +PP_INSTALL_DIR="$HOME/Library/MobileDevice/Provisioning Profiles" +PP_INSTALL_PATH="$PP_INSTALL_DIR/$PROFILE" + +ARTIFACTS_PATH="$(realpath .)/$artifacts_path" + +create_e2e_xcconfig() { + echo_subtitle "Create '$E2E_XCCONFIG_PATH'" + get_secret $DD_IOS_SECRET__E2E_XCCONFIG_BASE64 | base64 --decode -o $E2E_XCCONFIG_PATH + echo_succ "▸ '$E2E_XCCONFIG_PATH' ready" +} + +create_codesign_files() { + echo_subtitle "Create codesign files in '$E2E_CODESIGN_DIR'" + rm -rf "$E2E_CODESIGN_DIR" + mkdir -p "$E2E_CODESIGN_DIR" + get_secret $DD_IOS_SECRET__E2E_CERTIFICATE_P12_BASE64 | base64 --decode -o $P12_PATH + echo_succ "▸ $P12_PATH - ready" + get_secret $DD_IOS_SECRET__E2E_PROVISIONING_PROFILE_BASE64 | base64 --decode -o $PP_PATH + echo_succ "▸ $PP_PATH - ready" +} + +setup_codesigning() { + echo_subtitle "Setup code signing" + + # Create temporary keychain + if ! security delete-keychain "$KEYCHAIN" 2>/dev/null; then + echo_warn "▸ Keychain '$KEYCHAIN' not found, nothing to delete" + fi + if ! security create-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"; then + echo_err "▸ Error:" "Failed to create keychain '$KEYCHAIN'" + return 1 + fi + if ! security set-keychain-settings -lut 21600 "$KEYCHAIN"; then + echo_err "▸ Error:" "Failed to set keychain settings for '$KEYCHAIN'" + return 1 + fi + if ! security unlock-keychain -p "$KEYCHAIN_PASSWORD" "$KEYCHAIN"; then + echo "▸ Error:" "Failed to unlock keychain '$KEYCHAIN'" + return 1 + fi + echo_succ "▸ '$KEYCHAIN' created and unlocked" + + # Import certificate to keychain + P12_PASSWORD=$(get_secret "$DD_IOS_SECRET__E2E_CERTIFICATE_P12_PASSWORD") + if ! security import "$P12_PATH" -P "$P12_PASSWORD" -A -t cert -f pkcs12 -k "$KEYCHAIN"; then + echo_err "▸ Error:" "Failed to import certificate from '$P12_PATH' to '$KEYCHAIN'" + return 1 + fi + echo_succ "▸ '$P12_PATH' certificate imported to '$KEYCHAIN'" + + if ! security list-keychain -d user -s "$KEYCHAIN" "login.keychain" "System.keychain"; then + echo_err "▸ Error:" "Failed to configure keychain search list for '$KEYCHAIN'" + return 1 + fi + echo_succ "▸ '$KEYCHAIN' keychain search configured" + + if ! security set-key-partition-list -S apple-tool:,apple: -s -k "$KEYCHAIN_PASSWORD" "$KEYCHAIN" >/dev/null 2>&1; then + echo_err "▸ Error:" "Failed to set key partition list for '$KEYCHAIN'" + return 1 + fi + echo_succ "▸ Permission granted for '$KEYCHAIN' keychain" + + # Install provisioning profile + mkdir -p "$PP_INSTALL_DIR" + if ! cp "$PP_PATH" "$PP_INSTALL_PATH"; then + echo_err "▸ Error:" "Failed to install provisioning profile from '$PP_PATH' to '$PP_INSTALL_PATH'" + return 1 + fi + echo_succ "▸ '$PP_PATH' provisioning profile installed in '$PP_INSTALL_PATH'" +} + +cleanup_codesigning() { + echo_subtitle "Cleanup code signing" + + rm -f "$PP_INSTALL_PATH" + echo_info "▸ '$PP_INSTALL_PATH' deleted" + + if security delete-keychain "$KEYCHAIN" 2>/dev/null; then + echo_info "▸ '$KEYCHAIN' deleted" + else + echo_warn "▸ Keychain '$KEYCHAIN' not found or failed to delete" + fi +} + +create_e2e_xcconfig +create_codesign_files +trap cleanup_codesigning EXIT INT # clean up keychain on exit +setup_codesigning + +echo_subtitle "Run 'make clean archive export upload ARTIFACTS_PATH=\"$ARTIFACTS_PATH\"' in '$E2E_DIR'" +cd "$E2E_DIR" +make clean archive export ARTIFACTS_PATH="$ARTIFACTS_PATH" + +if [ "$DRY_RUN" = "1" ] || [ "$DRY_RUN" = "true" ]; then + echo_warn "Running in DRY RUN mode. Skipping 'make upload'." +else + export DATADOG_API_KEY=$(get_secret $DD_IOS_SECRET__E2E_S8S_API_KEY) + export DATADOG_APP_KEY=$(get_secret $DD_IOS_SECRET__E2E_S8S_APP_KEY) + export S8S_APPLICATION_ID=$(get_secret $DD_IOS_SECRET__E2E_S8S_APPLICATION_ID) + make upload ARTIFACTS_PATH="$ARTIFACTS_PATH" +fi diff --git a/tools/env-check.sh b/tools/env-check.sh index 089833b861..e1a201404e 100755 --- a/tools/env-check.sh +++ b/tools/env-check.sh @@ -79,6 +79,16 @@ if command -v brew >/dev/null 2>&1; then fi if [ "$CI" = "true" ]; then + echo "" + echo_succ "npm:" + check_if_installed npm + npm --version + + echo "" + echo_succ "datadog-ci:" + check_if_installed datadog-ci + datadog-ci version + # Check if all secrets are available: ./tools/secrets/check-secrets.sh diff --git a/tools/release/publish-github.sh b/tools/release/publish-github.sh index 0b778c9af8..03b788c56a 100755 --- a/tools/release/publish-github.sh +++ b/tools/release/publish-github.sh @@ -8,7 +8,9 @@ # --tag: The tag to publish GitHub asset to. # --artifacts-path: Path to build artifacts. # --overwrite-existing: Overwrite existing GH asset. -# --dry-run: Do everything except publishing the GitHub asset. + +# ENVs: +# - DRY_RUN: Set to '1' to do everything except publishing the GitHub asset. set -eo pipefail source ./tools/utils/argparse.sh @@ -18,7 +20,6 @@ source ./tools/secrets/get-secret.sh set_description "Publishes GitHub asset to GH release." define_arg "tag" "" "The tag to publish GitHub asset to." "string" "true" define_arg "overwrite-existing" "false" "Overwrite existing GH asset." "store_true" -define_arg "dry-run" "false" "Do everything except publishing the GitHub asset." "store_true" define_arg "artifacts-path" "" "Path to build artifacts." "string" "true" check_for_help "$@" diff --git a/tools/release/publish-podspec.sh b/tools/release/publish-podspec.sh index ed7a371364..043e0de3f0 100755 --- a/tools/release/publish-podspec.sh +++ b/tools/release/publish-podspec.sh @@ -8,6 +8,9 @@ # --artifacts-path: The path to build artifacts. # --podspec-name: The name of podspec file to publish. +# ENVs: +# - DRY_RUN: Set to '1' to do everything except publishing podspecs to Cocoapods trunk. + set -eo pipefail source ./tools/utils/argparse.sh source ./tools/utils/echo-color.sh diff --git a/tools/runner-setup.sh b/tools/runner-setup.sh index c0e9b32855..3870ff0762 100755 --- a/tools/runner-setup.sh +++ b/tools/runner-setup.sh @@ -12,6 +12,7 @@ # --watchOS: Flag that prepares the runner instance for watchOS testing. Disabled by default. # --os: Sets the expected OS version for installed simulators when --iOS, --tvOS, --visionOS or --watchOS flag is set. Default: '17.4'. # --ssh: Flag that adds ssh configuration for interacting with GitHub repositories. Disabled by default. +# --datadog-ci: Flag that installs 'datadog-ci' on the runner. Disabled by default. set -eo pipefail source ./tools/utils/echo-color.sh @@ -26,6 +27,7 @@ define_arg "visionOS" "false" "Flag that prepares the runner instance for vision define_arg "watchOS" "false" "Flag that prepares the runner instance for watchOS testing. Disabled by default." "store_true" define_arg "os" "17.4" "Sets the expected OS version for installed simulators when --iOS, --tvOS, --visionOS or --watchOS flag is set. Default: '17.4'." "string" "false" define_arg "ssh" "false" "Flag that adds ssh configuration for interacting with GitHub repositories. Disabled by default." "store_true" +define_arg "datadog-ci" "false" "Flag that installs 'datadog-ci' on the runner. Disabled by default." "store_true" check_for_help "$@" parse_args "$@" @@ -143,3 +145,16 @@ EOF echo_succ "Found both SSH key and SSH config file. Skipping..." fi fi + +if [ "$datadog_ci" = "true" ]; then + echo_subtitle "Supply datadog-ci" + echo "Check current runner for existing 'datadog-ci' installation:" + if ! command -v datadog-ci >/dev/null 2>&1; then + echo_warn "Found no 'datadog-ci'. Installing..." + npm install -g @datadog/datadog-ci + else + echo_succ "'datadog-ci' already installed. Skipping..." + echo "datadog-ci version:" + datadog-ci version + fi +fi diff --git a/tools/secrets/config.sh b/tools/secrets/config.sh index 527f03cc4d..8476beb4b4 100644 --- a/tools/secrets/config.sh +++ b/tools/secrets/config.sh @@ -15,10 +15,24 @@ DD_IOS_SECRET__TEST_SECRET="test.secret" DD_IOS_SECRET__GH_CLI_TOKEN="gh.cli.token" DD_IOS_SECRET__CP_TRUNK_TOKEN="cocoapods.trunk.token" DD_IOS_SECRET__SSH_KEY="ssh.key" +DD_IOS_SECRET__E2E_CERTIFICATE_P12_BASE64="e2e.certificate.p12.base64" +DD_IOS_SECRET__E2E_CERTIFICATE_P12_PASSWORD="e2e.certificate.p12.password" +DD_IOS_SECRET__E2E_PROVISIONING_PROFILE_BASE64="e2e.provisioning.profile.base64" +DD_IOS_SECRET__E2E_XCCONFIG_BASE64="e2e.xcconfig.base64" +DD_IOS_SECRET__E2E_S8S_API_KEY="e2e.s8s.api.key" +DD_IOS_SECRET__E2E_S8S_APP_KEY="e2e.s8s.app.key" +DD_IOS_SECRET__E2E_S8S_APPLICATION_ID="e2e.s8s.app.id" declare -A DD_IOS_SECRETS=( [0]="$DD_IOS_SECRET__TEST_SECRET | test secret to see if things work, free to change but not delete" [1]="$DD_IOS_SECRET__GH_CLI_TOKEN | GitHub token to authenticate 'gh' cli (https://cli.github.com/)" [2]="$DD_IOS_SECRET__CP_TRUNK_TOKEN | Cocoapods token to authenticate 'pod trunk' operations (https://guides.cocoapods.org/terminal/commands.html)" [3]="$DD_IOS_SECRET__SSH_KEY | SSH key to authenticate 'git clone git@github.com:...' operations" + [4]="$DD_IOS_SECRET__E2E_CERTIFICATE_P12_BASE64 | Base64-encoded '.p12' certificate file for signing E2E app" + [5]="$DD_IOS_SECRET__E2E_CERTIFICATE_P12_PASSWORD | Password to '$DD_IOS_SECRET__E2E_CERTIFICATE_P12_BASE64' certificate" + [6]="$DD_IOS_SECRET__E2E_PROVISIONING_PROFILE_BASE64 | Base64-encoded provisioning profile file for signing E2E app" + [7]="$DD_IOS_SECRET__E2E_XCCONFIG_BASE64 | Base64-encoded xcconfig file for E2E app" + [8]="$DD_IOS_SECRET__E2E_S8S_API_KEY | DATADOG_API_KEY for uploading E2E app to synthetics" + [9]="$DD_IOS_SECRET__E2E_S8S_APP_KEY | DATADOG_APP_KEY for uploading E2E app to synthetics" + [10]="$DD_IOS_SECRET__E2E_S8S_APPLICATION_ID | Synthetics app ID for E2E tests" ) diff --git a/tools/secrets/get-secret.sh b/tools/secrets/get-secret.sh index 7e15c77645..594568b5de 100755 --- a/tools/secrets/get-secret.sh +++ b/tools/secrets/get-secret.sh @@ -17,7 +17,7 @@ get_secret() { vault login -method=aws -no-print else if vault token lookup &>/dev/null; then - echo_succ "Reading '$secret_name' secret in local env. You are already authenticated with 'vault'." >&2 + echo "Reading '$secret_name' secret in local env. You are already authenticated with 'vault'." >&2 else echo_warn "Reading '$secret_name' secret in local env. You will now be authenticated with OIDC in your web browser." >&2 vault login -method=oidc -no-print diff --git a/tools/secrets/set-secret.sh b/tools/secrets/set-secret.sh index 7957230761..cee758bd6c 100755 --- a/tools/secrets/set-secret.sh +++ b/tools/secrets/set-secret.sh @@ -45,6 +45,8 @@ get_secret_value_from_input() { } get_secret_value_from_file() { + local base64_encode="$1" + echo_info "Enter the file path to read the value for '$SECRET_NAME':" read "SECRET_FILE" echo @@ -53,7 +55,12 @@ get_secret_value_from_file() { echo_info "Using '$SECRET_FILE'" if [[ -f "$SECRET_FILE" ]]; then - SECRET_VALUE=$(cat "$SECRET_FILE") + if [ "$base64_encode" = "true" ]; then + echo_info "Encoding value with base64" + SECRET_VALUE=$(cat "$SECRET_FILE" | base64) + else + SECRET_VALUE=$(cat "$SECRET_FILE") + fi else echo_err "Error: File '$SECRET_FILE' does not exist." exit 1 @@ -64,9 +71,10 @@ select_input_method() { echo echo_info "How would you like to provide the secret value?" echo "1) Enter manually" - echo "2) Read from a file" + echo "2) Read from text file" + echo "3) Read from arbitrary file and encode with base64" while true; do - echo_info "Enter your choice (1 or 2):" + echo_info "Enter your choice:" read "input_method" case $input_method in 1) @@ -77,8 +85,12 @@ select_input_method() { get_secret_value_from_file break ;; + 3) + get_secret_value_from_file "true" + break + ;; *) - echo_err "Invalid choice. Please enter 1 or 2." + echo_err "Invalid choice." ;; esac done diff --git a/tools/utils/common.mk b/tools/utils/common.mk index a4713b84f8..2fdee82c32 100644 --- a/tools/utils/common.mk +++ b/tools/utils/common.mk @@ -27,4 +27,5 @@ endef CURRENT_GIT_TAG := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print-tag) CURRENT_GIT_BRANCH := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print-branch) +CURRENT_GIT_COMMIT_SHORT := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print-commit-short) CURRENT_GIT_REF := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print) diff --git a/tools/utils/current-git.sh b/tools/utils/current-git.sh index d71c057dd8..4a1cc7474b 100755 --- a/tools/utils/current-git.sh +++ b/tools/utils/current-git.sh @@ -28,6 +28,16 @@ function current_git_branch() { fi } +# Prints current commit sha (short) +function current_git_commit_short() { + if [[ -n "$CI_COMMIT_SHORT_SHA" ]]; then + echo "$CI_COMMIT_SHORT_SHA" + else + local git_commit_short=$(git rev-parse --short HEAD) + echo "$git_commit_short" + fi +} + # Prints current tag (if any) and current branch otherwise. function current_git_ref() { local tag=$(current_git_tag) @@ -48,6 +58,9 @@ case "$1" in --print-branch) current_git_branch ;; + --print-commit-short) + current_git_commit_short + ;; *) ;; esac From d03a5bf9722fffe0b1b56f69d884588cee47d829 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 17 Jul 2024 13:11:57 +0200 Subject: [PATCH 27/43] RUM-5248 feat: send memory warning as RUM error --- CHANGELOG.md | 1 + Datadog/Datadog.xcodeproj/project.pbxproj | 46 +++++++++++++++++ DatadogCore/Sources/Core/MessageBus.swift | 2 +- .../Sources/RUM/RUMDataModels+objc.swift | 5 +- .../Sources/DataModels/RUMDataModels.swift | 3 +- DatadogRUM/Sources/Feature/RUMFeature.swift | 9 +++- .../MemoryWarnings/MemoryWarning.swift | 30 +++++++++++ .../MemoryWarnings/MemoryWarningMonitor.swift | 50 +++++++++++++++++++ .../MemoryWarningReporter.swift | 50 +++++++++++++++++++ .../Instrumentation/RUMInstrumentation.swift | 9 +++- .../Sources/RUMMonitor/RUMCommand.swift | 20 ++++++++ .../MemoryWarnings/MemoryWarningMocks.swift | 41 +++++++++++++++ .../MemoryWarningMonitorTests.swift | 44 ++++++++++++++++ .../RUMInstrumentationTests.swift | 21 +++++--- 14 files changed, 319 insertions(+), 12 deletions(-) create mode 100644 DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarning.swift create mode 100644 DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarningMonitor.swift create mode 100644 DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarningReporter.swift create mode 100644 DatadogRUM/Tests/Instrumentation/MemoryWarnings/MemoryWarningMocks.swift create mode 100644 DatadogRUM/Tests/Instrumentation/MemoryWarnings/MemoryWarningMonitorTests.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index d24b8927d2..aef6a1fe42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ - [FEATURE] Enable DatadogCore, DatadogLogs and DatadogTrace to compile on watchOS platform. See [#1918][] (Thanks [@jfiser-paylocity][]) [#1946][] - [IMPROVEMENT] Ability to clear feature data storage using `clearAllData` API. See [#1940][] +- [IMPROVEMENT] Send memory warning as RUM error. # 2.14.1 / 09-07-2024 diff --git a/Datadog/Datadog.xcodeproj/project.pbxproj b/Datadog/Datadog.xcodeproj/project.pbxproj index ff36be4e1b..42c37ce795 100644 --- a/Datadog/Datadog.xcodeproj/project.pbxproj +++ b/Datadog/Datadog.xcodeproj/project.pbxproj @@ -47,6 +47,16 @@ 3C41693C29FBF4D50042B9D2 /* DatadogWebViewTracking.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3CE119FE29F7BE0100202522 /* DatadogWebViewTracking.framework */; }; 3C43A3882C188974000BFB21 /* WatchdogTerminationMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C43A3862C188970000BFB21 /* WatchdogTerminationMonitorTests.swift */; }; 3C43A3892C188975000BFB21 /* WatchdogTerminationMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C43A3862C188970000BFB21 /* WatchdogTerminationMonitorTests.swift */; }; + 3C4CF9912C47BE07006DE1C0 /* MemoryWarningMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5CD8C12C3EBA1700B12303 /* MemoryWarningMonitor.swift */; }; + 3C4CF9922C47BE07006DE1C0 /* MemoryWarningMonitor.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5CD8C12C3EBA1700B12303 /* MemoryWarningMonitor.swift */; }; + 3C4CF9942C47CAE9006DE1C0 /* MemoryWarning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5CD8C42C3EC61500B12303 /* MemoryWarning.swift */; }; + 3C4CF9952C47CAEA006DE1C0 /* MemoryWarning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5CD8C42C3EC61500B12303 /* MemoryWarning.swift */; }; + 3C4CF9982C47CC91006DE1C0 /* MemoryWarningMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4CF9972C47CC8C006DE1C0 /* MemoryWarningMonitorTests.swift */; }; + 3C4CF9992C47CC92006DE1C0 /* MemoryWarningMonitorTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4CF9972C47CC8C006DE1C0 /* MemoryWarningMonitorTests.swift */; }; + 3C4CF99B2C47DAA5006DE1C0 /* MemoryWarningMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4CF99A2C47DAA5006DE1C0 /* MemoryWarningMocks.swift */; }; + 3C4CF99C2C47DAA5006DE1C0 /* MemoryWarningMocks.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C4CF99A2C47DAA5006DE1C0 /* MemoryWarningMocks.swift */; }; + 3C5CD8CD2C3ECB9400B12303 /* MemoryWarningReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5CD8CA2C3ECB4800B12303 /* MemoryWarningReporter.swift */; }; + 3C5CD8CE2C3ECB9400B12303 /* MemoryWarningReporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5CD8CA2C3ECB4800B12303 /* MemoryWarningReporter.swift */; }; 3C5D63692B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */; }; 3C5D636A2B55512B00FEB4BA /* OTelTraceState+Datadog.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */; }; 3C5D636C2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */; }; @@ -2108,6 +2118,11 @@ 3C33E4062BEE35A7003B2988 /* RUMContextMocks.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = RUMContextMocks.swift; sourceTree = ""; }; 3C3EF2AF2C1AEBAB009E9E57 /* LaunchReport.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LaunchReport.swift; sourceTree = ""; }; 3C43A3862C188970000BFB21 /* WatchdogTerminationMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchdogTerminationMonitorTests.swift; sourceTree = ""; }; + 3C4CF9972C47CC8C006DE1C0 /* MemoryWarningMonitorTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryWarningMonitorTests.swift; sourceTree = ""; }; + 3C4CF99A2C47DAA5006DE1C0 /* MemoryWarningMocks.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryWarningMocks.swift; sourceTree = ""; }; + 3C5CD8C12C3EBA1700B12303 /* MemoryWarningMonitor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryWarningMonitor.swift; sourceTree = ""; }; + 3C5CD8C42C3EC61500B12303 /* MemoryWarning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryWarning.swift; sourceTree = ""; }; + 3C5CD8CA2C3ECB4800B12303 /* MemoryWarningReporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoryWarningReporter.swift; sourceTree = ""; }; 3C5D63682B55512B00FEB4BA /* OTelTraceState+Datadog.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+Datadog.swift"; sourceTree = ""; }; 3C5D636B2B55513500FEB4BA /* OTelTraceState+DatadogTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "OTelTraceState+DatadogTests.swift"; sourceTree = ""; }; 3C62C3602C3E852F00C7E336 /* MultiSelector.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MultiSelector.swift; sourceTree = ""; }; @@ -3406,6 +3421,25 @@ /* End PBXFrameworksBuildPhase section */ /* Begin PBXGroup section */ + 3C4CF9932C47BE10006DE1C0 /* MemoryWarnings */ = { + isa = PBXGroup; + children = ( + 3C5CD8C42C3EC61500B12303 /* MemoryWarning.swift */, + 3C5CD8C12C3EBA1700B12303 /* MemoryWarningMonitor.swift */, + 3C5CD8CA2C3ECB4800B12303 /* MemoryWarningReporter.swift */, + ); + path = MemoryWarnings; + sourceTree = ""; + }; + 3C4CF9962C47CC72006DE1C0 /* MemoryWarnings */ = { + isa = PBXGroup; + children = ( + 3C4CF9972C47CC8C006DE1C0 /* MemoryWarningMonitorTests.swift */, + 3C4CF99A2C47DAA5006DE1C0 /* MemoryWarningMocks.swift */, + ); + path = MemoryWarnings; + sourceTree = ""; + }; 3C68FCD12C05EE8E00723696 /* WatchdogTerminations */ = { isa = PBXGroup; children = ( @@ -4814,6 +4848,7 @@ 616CCE11250A181C009FED46 /* Instrumentation */ = { isa = PBXGroup; children = ( + 3C4CF9932C47BE10006DE1C0 /* MemoryWarnings */, 616CCE12250A1868009FED46 /* RUMCommandSubscriber.swift */, 616CCE15250A467E009FED46 /* RUMInstrumentation.swift */, 61F3CDA1251118DD00C816E5 /* Views */, @@ -5481,6 +5516,7 @@ 61F3CDA825121F8F00C816E5 /* Instrumentation */ = { isa = PBXGroup; children = ( + 3C4CF9962C47CC72006DE1C0 /* MemoryWarnings */, 61F3CDA925121FA100C816E5 /* Views */, 6141014C251A577D00E3C2D9 /* Actions */, 613F23EF252B1287006CD2D7 /* Resources */, @@ -8710,8 +8746,10 @@ 61C713AB2A3B790B00FA735A /* Monitor.swift in Sources */, D23F8E6429DDCD28001CFAE8 /* SwiftUIViewHandler.swift in Sources */, 3CFF4F922C09E630006F191D /* WatchdogTerminationAppStateManager.swift in Sources */, + 3C4CF9952C47CAEA006DE1C0 /* MemoryWarning.swift in Sources */, D23F8E6529DDCD28001CFAE8 /* RUMFeature.swift in Sources */, D23F8E6629DDCD28001CFAE8 /* RUMDebugging.swift in Sources */, + 3C4CF9912C47BE07006DE1C0 /* MemoryWarningMonitor.swift in Sources */, D23F8E6729DDCD28001CFAE8 /* RUMUUID.swift in Sources */, D23F8E6829DDCD28001CFAE8 /* UIKitExtensions.swift in Sources */, 61C713A82A3B78F900FA735A /* RUMMonitorProtocol+Convenience.swift in Sources */, @@ -8755,6 +8793,7 @@ D23F8E8229DDCD28001CFAE8 /* RUMSessionScope.swift in Sources */, D23F8E8329DDCD28001CFAE8 /* RUMUser.swift in Sources */, D23F8E8429DDCD28001CFAE8 /* UIKitRUMUserActionsPredicate.swift in Sources */, + 3C5CD8CE2C3ECB9400B12303 /* MemoryWarningReporter.swift in Sources */, D23F8E8529DDCD28001CFAE8 /* SwiftUIExtensions.swift in Sources */, 3CFF4F952C09E63C006F191D /* WatchdogTerminationChecker.swift in Sources */, D23F8E8629DDCD28001CFAE8 /* RUMDataModelsMapping.swift in Sources */, @@ -8782,6 +8821,7 @@ D23F8EA029DDCD38001CFAE8 /* RUMOffViewEventsHandlingRuleTests.swift in Sources */, 61C4534B2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */, D23F8EA229DDCD38001CFAE8 /* RUMSessionScopeTests.swift in Sources */, + 3C4CF9992C47CC92006DE1C0 /* MemoryWarningMonitorTests.swift in Sources */, D23F8EA329DDCD38001CFAE8 /* RUMUserActionScopeTests.swift in Sources */, 615B0F8C2BB33C2800E9ED6C /* AppHangsMonitorTests.swift in Sources */, 61C713B42A3C3A0B00FA735A /* RUMMonitorProtocol+InternalTests.swift in Sources */, @@ -8801,6 +8841,7 @@ 61C713B72A3C600400FA735A /* RUMMonitorProtocol+ConvenienceTests.swift in Sources */, D23F8EB129DDCD38001CFAE8 /* RUMViewScopeTests.swift in Sources */, D224431029E977A100274EC7 /* TelemetryReceiverTests.swift in Sources */, + 3C4CF99C2C47DAA5006DE1C0 /* MemoryWarningMocks.swift in Sources */, 3C43A3892C188975000BFB21 /* WatchdogTerminationMonitorTests.swift in Sources */, D23F8EB229DDCD38001CFAE8 /* ValuePublisherTests.swift in Sources */, 6174D61B2BFE449300EC7469 /* SessionEndedMetricTests.swift in Sources */, @@ -9039,8 +9080,10 @@ 61C713AA2A3B790B00FA735A /* Monitor.swift in Sources */, D29A9F8529DD85BB005C54A4 /* SwiftUIViewHandler.swift in Sources */, 3CFF4F912C09E630006F191D /* WatchdogTerminationAppStateManager.swift in Sources */, + 3C4CF9942C47CAE9006DE1C0 /* MemoryWarning.swift in Sources */, D29A9F7429DD85BB005C54A4 /* RUMFeature.swift in Sources */, D29A9F7729DD85BB005C54A4 /* RUMDebugging.swift in Sources */, + 3C4CF9922C47BE07006DE1C0 /* MemoryWarningMonitor.swift in Sources */, D29A9F6E29DD85BB005C54A4 /* RUMUUID.swift in Sources */, D29A9F8D29DD8665005C54A4 /* UIKitExtensions.swift in Sources */, 61C713A72A3B78F900FA735A /* RUMMonitorProtocol+Convenience.swift in Sources */, @@ -9084,6 +9127,7 @@ D29A9F5C29DD85BB005C54A4 /* RUMSessionScope.swift in Sources */, D29A9F6629DD85BB005C54A4 /* RUMUser.swift in Sources */, D29A9F8229DD85BB005C54A4 /* UIKitRUMUserActionsPredicate.swift in Sources */, + 3C5CD8CD2C3ECB9400B12303 /* MemoryWarningReporter.swift in Sources */, D29A9F8E29DD8665005C54A4 /* SwiftUIExtensions.swift in Sources */, 3CFF4F942C09E63C006F191D /* WatchdogTerminationChecker.swift in Sources */, D29A9F7829DD85BB005C54A4 /* RUMDataModelsMapping.swift in Sources */, @@ -9111,6 +9155,7 @@ D29A9FA629DDB483005C54A4 /* RUMOffViewEventsHandlingRuleTests.swift in Sources */, 61C4534A2C0A0BBF00CC4C17 /* TelemetryInterceptorTests.swift in Sources */, D29A9FBD29DDB483005C54A4 /* RUMSessionScopeTests.swift in Sources */, + 3C4CF9982C47CC91006DE1C0 /* MemoryWarningMonitorTests.swift in Sources */, D29A9FAB29DDB483005C54A4 /* RUMUserActionScopeTests.swift in Sources */, 615B0F8B2BB33C2800E9ED6C /* AppHangsMonitorTests.swift in Sources */, 61C713B32A3C3A0B00FA735A /* RUMMonitorProtocol+InternalTests.swift in Sources */, @@ -9130,6 +9175,7 @@ 61C713B62A3C600400FA735A /* RUMMonitorProtocol+ConvenienceTests.swift in Sources */, D29A9FB829DDB483005C54A4 /* RUMViewScopeTests.swift in Sources */, D224430F29E9779F00274EC7 /* TelemetryReceiverTests.swift in Sources */, + 3C4CF99B2C47DAA5006DE1C0 /* MemoryWarningMocks.swift in Sources */, 3C43A3882C188974000BFB21 /* WatchdogTerminationMonitorTests.swift in Sources */, D29A9F9D29DDB483005C54A4 /* ValuePublisherTests.swift in Sources */, 6174D61A2BFE449300EC7469 /* SessionEndedMetricTests.swift in Sources */, diff --git a/DatadogCore/Sources/Core/MessageBus.swift b/DatadogCore/Sources/Core/MessageBus.swift index cd446ccbee..c264236fb6 100644 --- a/DatadogCore/Sources/Core/MessageBus.swift +++ b/DatadogCore/Sources/Core/MessageBus.swift @@ -72,7 +72,7 @@ internal final class MessageBus { } /// Removes the given key and its associated receiver from the bus. - /// + /// /// - Parameter key: The key to remove along with its associated receiver. func removeReceiver(forKey key: String) { queue.async { self.bus.removeValue(forKey: key) } diff --git a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift index 433a90c8b6..e24fcee3dd 100644 --- a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift +++ b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift @@ -1732,6 +1732,7 @@ public enum DDRUMErrorEventErrorCategory: Int { case .appHang?: self = .appHang case .exception?: self = .exception case .watchdogTermination?: self = .watchdogTermination + case .memoryWarning?: self = .memoryWarning } } @@ -1742,6 +1743,7 @@ public enum DDRUMErrorEventErrorCategory: Int { case .appHang: return .appHang case .exception: return .exception case .watchdogTermination: return .watchdogTermination + case .memoryWarning: return .memoryWarning } } @@ -1750,6 +1752,7 @@ public enum DDRUMErrorEventErrorCategory: Int { case appHang case exception case watchdogTermination + case memoryWarning } @objc @@ -7707,4 +7710,4 @@ public class DDTelemetryConfigurationEventView: NSObject { // swiftlint:enable force_unwrapping -// Generated from https://github.com/DataDog/rum-events-format/tree/ae8c30a094339995e234fd55831ade0999bf0612 +// Generated from https://github.com/DataDog/rum-events-format/tree/c853be6db33125c8767ae563d2af47b92636c4e1 diff --git a/DatadogRUM/Sources/DataModels/RUMDataModels.swift b/DatadogRUM/Sources/DataModels/RUMDataModels.swift index bb1b5fe084..ce17a786c6 100644 --- a/DatadogRUM/Sources/DataModels/RUMDataModels.swift +++ b/DatadogRUM/Sources/DataModels/RUMDataModels.swift @@ -807,6 +807,7 @@ public struct RUMErrorEvent: RUMDataModel { case appHang = "App Hang" case exception = "Exception" case watchdogTermination = "Watchdog Termination" + case memoryWarning = "Memory Warning" } /// Properties for one of the error causes @@ -4334,4 +4335,4 @@ public struct RUMTelemetryOperatingSystem: Codable { } } -// Generated from https://github.com/DataDog/rum-events-format/tree/ae8c30a094339995e234fd55831ade0999bf0612 +// Generated from https://github.com/DataDog/rum-events-format/tree/c853be6db33125c8767ae563d2af47b92636c4e1 diff --git a/DatadogRUM/Sources/Feature/RUMFeature.swift b/DatadogRUM/Sources/Feature/RUMFeature.swift index 1f4fab6f45..f45231198b 100644 --- a/DatadogRUM/Sources/Feature/RUMFeature.swift +++ b/DatadogRUM/Sources/Feature/RUMFeature.swift @@ -108,6 +108,12 @@ internal final class RUMFeature: DatadogRemoteFeature { dateProvider: configuration.dateProvider ) + let memoryWarningReporter = MemoryWarningReporter() + let memoryWarningMonitor = MemoryWarningMonitor( + backtraceReporter: core.backtraceReporter, + memoryWarningReporter: memoryWarningReporter + ) + self.instrumentation = RUMInstrumentation( featureScope: featureScope, uiKitRUMViewsPredicate: configuration.uiKitViewsPredicate, @@ -119,7 +125,8 @@ internal final class RUMFeature: DatadogRemoteFeature { backtraceReporter: core.backtraceReporter, fatalErrorContext: dependencies.fatalErrorContext, processID: configuration.processID, - watchdogTermination: watchdogTermination + watchdogTermination: watchdogTermination, + memoryWarningMonitor: memoryWarningMonitor ) self.requestBuilder = RequestBuilder( customIntakeURL: configuration.customEndpoint, diff --git a/DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarning.swift b/DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarning.swift new file mode 100644 index 0000000000..e405f46d3b --- /dev/null +++ b/DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarning.swift @@ -0,0 +1,30 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation +import DatadogInternal +import UIKit + +/// Represents a memory warning +internal struct MemoryWarning { + /// The date when the memory warning was received. + let date: Date + + /// The backtrace at the moment of memory warning. + let backtrace: BacktraceReport? + + /// Creates a new instance of `MemoryWarning + /// - Parameters: + /// - date: Date when the memory warning was received. + /// - backtrace: Backtrace at the moment of memory warning. + init( + date: Date, + backtrace: BacktraceReport? + ) { + self.date = date + self.backtrace = backtrace + } +} diff --git a/DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarningMonitor.swift b/DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarningMonitor.swift new file mode 100644 index 0000000000..06aa9ac492 --- /dev/null +++ b/DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarningMonitor.swift @@ -0,0 +1,50 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import Foundation +import DatadogInternal +import UIKit + +/// Tracks the memory warnings history and publishes it to the subscribers. +internal final class MemoryWarningMonitor { + let notificationCenter: NotificationCenter + let backtraceReporter: BacktraceReporting? + let reporter: MemoryWarningReporting + + init( + backtraceReporter: BacktraceReporting?, + memoryWarningReporter: MemoryWarningReporting, + notificationCenter: NotificationCenter = .default + ) { + self.notificationCenter = notificationCenter + self.backtraceReporter = backtraceReporter + self.reporter = memoryWarningReporter + } + + /// Starts monitoring memory warnings by subscribing to `UIApplication.didReceiveMemoryWarningNotification`. + func start() { + notificationCenter.addObserver(self, selector: #selector(didReceiveMemoryWarning), name: UIApplication.didReceiveMemoryWarningNotification, object: nil) + } + + @objc + func didReceiveMemoryWarning() { + let date: Date = .init() + let backtrace: BacktraceReport? + do { + backtrace = try backtraceReporter?.generateBacktrace() + } catch { + backtrace = nil + } + let warning = MemoryWarning(date: date, backtrace: backtrace) + + reporter.report(warning: warning) + } + + /// Stops monitoring memory warnings. + func stop() { + notificationCenter.removeObserver(self) + } +} diff --git a/DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarningReporter.swift b/DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarningReporter.swift new file mode 100644 index 0000000000..ea925f04db --- /dev/null +++ b/DatadogRUM/Sources/Instrumentation/MemoryWarnings/MemoryWarningReporter.swift @@ -0,0 +1,50 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-2020 Datadog, Inc. + */ + +import Foundation +import DatadogInternal +import UIKit + +/// Defines operations used for reporting memory warnings. +internal protocol MemoryWarningReporting: RUMCommandPublisher { + /// Reports the given memory warning. + /// - Parameter warning: The memory warning to report. + func report(warning: MemoryWarning) +} + +/// Receives memory warnings and reports them as RUM errors. +internal class MemoryWarningReporter: MemoryWarningReporting { + enum Constants { + /// The standardized `error.message` for RUM errors describing a memory warning. + static let memoryWarningErrorMessage = "Memory Warning" + /// The standardized `error.type` for RUM errors describing a memory warning. + static let memoryWarningErrorType = "MemoryWarning" + /// The standardized `error.stack` when backtrace generation was not available. + static let memoryWarningStackNotAvailableErrorMessage = "Stack trace was not generated because `DatadogCrashReporting` had not been enabled." + } + + private(set) weak var subscriber: RUMCommandSubscriber? + + /// Reports the given memory warning as a RUM error. + /// - Parameter warning: The memory warning to report. + func report(warning: MemoryWarning) { + let command = RUMAddCurrentViewMemoryWarningCommand( + time: warning.date, + attributes: [:], + message: Constants.memoryWarningErrorMessage, + type: Constants.memoryWarningErrorType, + stack: warning.backtrace?.stack ?? Constants.memoryWarningStackNotAvailableErrorMessage, + threads: warning.backtrace?.threads, + binaryImages: warning.backtrace?.binaryImages, + isStackTraceTruncated: warning.backtrace?.wasTruncated + ) + subscriber?.process(command: command) + } + + func publish(to subscriber: any RUMCommandSubscriber) { + self.subscriber = subscriber + } +} diff --git a/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift b/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift index 8b86fb09c6..3988617e39 100644 --- a/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift +++ b/DatadogRUM/Sources/Instrumentation/RUMInstrumentation.swift @@ -40,6 +40,8 @@ internal final class RUMInstrumentation: RUMCommandPublisher { /// Instruments Watchdog Terminations. let watchdogTermination: WatchdogTerminationMonitor? + let memoryWarningMonitor: MemoryWarningMonitor? + // MARK: - Initialization init( @@ -53,7 +55,8 @@ internal final class RUMInstrumentation: RUMCommandPublisher { backtraceReporter: BacktraceReporting, fatalErrorContext: FatalErrorContextNotifying, processID: UUID, - watchdogTermination: WatchdogTerminationMonitor? + watchdogTermination: WatchdogTerminationMonitor?, + memoryWarningMonitor: MemoryWarningMonitor ) { // Always create views handler (we can't know if it will be used by SwiftUI instrumentation) // and only swizzle `UIViewController` if UIKit instrumentation is configured: @@ -125,12 +128,14 @@ internal final class RUMInstrumentation: RUMCommandPublisher { self.longTasks = longTasks self.appHangs = appHangs self.watchdogTermination = watchdogTermination + self.memoryWarningMonitor = memoryWarningMonitor // Enable configured instrumentations: self.viewControllerSwizzler?.swizzle() self.uiApplicationSwizzler?.swizzle() self.longTasks?.start() self.appHangs?.start() + self.memoryWarningMonitor?.start() } deinit { @@ -140,6 +145,7 @@ internal final class RUMInstrumentation: RUMCommandPublisher { longTasks?.stop() appHangs?.stop() watchdogTermination?.stop() + memoryWarningMonitor?.stop() } func publish(to subscriber: RUMCommandSubscriber) { @@ -147,5 +153,6 @@ internal final class RUMInstrumentation: RUMCommandPublisher { actionsHandler?.publish(to: subscriber) longTasks?.publish(to: subscriber) appHangs?.nonFatalHangsHandler.publish(to: subscriber) + memoryWarningMonitor?.reporter.publish(to: subscriber) } } diff --git a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift index 0be97fcd21..1cde361874 100644 --- a/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift +++ b/DatadogRUM/Sources/RUMMonitor/RUMCommand.swift @@ -245,6 +245,26 @@ internal struct RUMAddCurrentViewAppHangCommand: RUMErrorCommand { let missedEventType: SessionEndedMetric.MissedEventType? = .error } +internal struct RUMAddCurrentViewMemoryWarningCommand: RUMErrorCommand { + var time: Date + var attributes: [AttributeKey: AttributeValue] + let canStartBackgroundView = false + let isUserInteraction = false + + let message: String + let type: String? + let stack: String? + let category: RUMErrorCategory = .memoryWarning + let isCrash: Bool? = false + let source: RUMInternalErrorSource = .source + let errorSourceType: RUMErrorEvent.Error.SourceType = .ios + let threads: [DDThread]? + let binaryImages: [BinaryImage]? + let isStackTraceTruncated: Bool? + + let missedEventType: SessionEndedMetric.MissedEventType? = .error +} + internal struct RUMAddViewTimingCommand: RUMCommand, RUMViewScopePropagatableAttributes { var time: Date var attributes: [AttributeKey: AttributeValue] diff --git a/DatadogRUM/Tests/Instrumentation/MemoryWarnings/MemoryWarningMocks.swift b/DatadogRUM/Tests/Instrumentation/MemoryWarnings/MemoryWarningMocks.swift new file mode 100644 index 0000000000..a72e7a8d4a --- /dev/null +++ b/DatadogRUM/Tests/Instrumentation/MemoryWarnings/MemoryWarningMocks.swift @@ -0,0 +1,41 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +@testable import DatadogRUM +import TestUtilities + +final class MemoryWarningReporterMock: MemoryWarningReporting { + let didReport: (MemoryWarning) -> Void + + init(didReport: @escaping (MemoryWarning) -> Void) { + self.didReport = didReport + } + + func report(warning: DatadogRUM.MemoryWarning) { + didReport(warning) + } + + /// nop + func publish(to subscriber: any DatadogRUM.RUMCommandSubscriber) { + } +} + +extension MemoryWarningMonitor: RandomMockable { + public static func mockRandom() -> MemoryWarningMonitor { + return .init( + backtraceReporter: nil, + memoryWarningReporter: MemoryWarningReporterMock.mockRandom(), + notificationCenter: .default + ) + } +} + +extension MemoryWarningReporterMock: RandomMockable { + static func mockRandom() -> MemoryWarningReporterMock { + return .init { _ in } + } +} diff --git a/DatadogRUM/Tests/Instrumentation/MemoryWarnings/MemoryWarningMonitorTests.swift b/DatadogRUM/Tests/Instrumentation/MemoryWarnings/MemoryWarningMonitorTests.swift new file mode 100644 index 0000000000..dfdd682d55 --- /dev/null +++ b/DatadogRUM/Tests/Instrumentation/MemoryWarnings/MemoryWarningMonitorTests.swift @@ -0,0 +1,44 @@ +/* + * Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. + * This product includes software developed at Datadog (https://www.datadoghq.com/). + * Copyright 2019-Present Datadog, Inc. + */ + +import XCTest +@testable import DatadogRUM + +final class MemoryWarningMonitorTests: XCTestCase { + // swiftlint:disable implicitly_unwrapped_optional + var sut: MemoryWarningMonitor! + // swiftlint:enable implicitly_unwrapped_optional + let notificationCenter = NotificationCenter() + + func testStart_memoryWarningReported() throws { + let didReport = expectation(description: "Memory warning reported") + let memoryWarningMock = MemoryWarningReporterMock { _ in + didReport.fulfill() + } + sut = .init( + backtraceReporter: nil, + memoryWarningReporter: memoryWarningMock, + notificationCenter: notificationCenter + ) + sut.start() + notificationCenter.post(name: UIApplication.didReceiveMemoryWarningNotification, object: nil) + wait(for: [didReport], timeout: 0.5) + } + + func testStop_memoryWarningNotReported() { + let memoryWarningMock = MemoryWarningReporterMock { _ in + XCTFail("Memory warning should not be reported after `stop()`") + } + sut = .init( + backtraceReporter: nil, + memoryWarningReporter: memoryWarningMock, + notificationCenter: notificationCenter + ) + sut.start() + sut.stop() + notificationCenter.post(name: UIApplication.didReceiveMemoryWarningNotification, object: nil) + } +} diff --git a/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift b/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift index 3f404d6bab..ac9e4c65d1 100644 --- a/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift +++ b/DatadogRUM/Tests/Instrumentation/RUMInstrumentationTests.swift @@ -25,7 +25,8 @@ class RUMInstrumentationTests: XCTestCase { backtraceReporter: BacktraceReporterMock(), fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny(), - watchdogTermination: .mockRandom() + watchdogTermination: .mockRandom(), + memoryWarningMonitor: .mockRandom() ) // Then @@ -51,7 +52,8 @@ class RUMInstrumentationTests: XCTestCase { backtraceReporter: BacktraceReporterMock(), fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny(), - watchdogTermination: .mockRandom() + watchdogTermination: .mockRandom(), + memoryWarningMonitor: .mockRandom() ) // Then @@ -74,7 +76,8 @@ class RUMInstrumentationTests: XCTestCase { backtraceReporter: BacktraceReporterMock(), fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny(), - watchdogTermination: .mockRandom() + watchdogTermination: .mockRandom(), + memoryWarningMonitor: .mockRandom() ) // Then @@ -100,7 +103,8 @@ class RUMInstrumentationTests: XCTestCase { backtraceReporter: BacktraceReporterMock(), fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny(), - watchdogTermination: .mockRandom() + watchdogTermination: .mockRandom(), + memoryWarningMonitor: .mockRandom() ) // Then @@ -122,7 +126,8 @@ class RUMInstrumentationTests: XCTestCase { backtraceReporter: BacktraceReporterMock(), fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny(), - watchdogTermination: .mockRandom() + watchdogTermination: .mockRandom(), + memoryWarningMonitor: .mockRandom() ) // Then @@ -144,7 +149,8 @@ class RUMInstrumentationTests: XCTestCase { backtraceReporter: BacktraceReporterMock(), fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny(), - watchdogTermination: .mockRandom() + watchdogTermination: .mockRandom(), + memoryWarningMonitor: .mockRandom() ) // Then @@ -166,7 +172,8 @@ class RUMInstrumentationTests: XCTestCase { backtraceReporter: BacktraceReporterMock(), fatalErrorContext: FatalErrorContextNotifierMock(), processID: .mockAny(), - watchdogTermination: .mockRandom() + watchdogTermination: .mockRandom(), + memoryWarningMonitor: .mockRandom() ) let subscriber = RUMCommandSubscriberMock() From 791fde96879e5e5f02714741a78007a254860e54 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 17 Jul 2024 14:03:31 +0200 Subject: [PATCH 28/43] RUM-1000 chore: use throwing API to create the file --- DatadogCore/Sources/Core/Storage/Files/Directory.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/DatadogCore/Sources/Core/Storage/Files/Directory.swift b/DatadogCore/Sources/Core/Storage/Files/Directory.swift index 1e8712adc3..dabb9f61f0 100644 --- a/DatadogCore/Sources/Core/Storage/Files/Directory.swift +++ b/DatadogCore/Sources/Core/Storage/Files/Directory.swift @@ -7,6 +7,10 @@ import Foundation import DatadogInternal +extension Data { + static let empty = Data() +} + /// Provides interfaces for accessing common properties and operations for a directory. internal protocol DirectoryProtocol: FileProtocol { /// Returns list of subdirectories in the directory. @@ -106,9 +110,7 @@ internal struct Directory: DirectoryProtocol { /// Creates file with given name. func createFile(named fileName: String) throws -> File { let fileURL = url.appendingPathComponent(fileName, isDirectory: false) - guard FileManager.default.createFile(atPath: fileURL.path, contents: nil, attributes: nil) == true else { - throw InternalError(description: "Cannot create file at path: \(fileURL.path)") - } + try Data.empty.write(to: fileURL, options: .atomic) return File(url: fileURL) } From 201b40247c518297de875597415402f019c61327 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 18 Jul 2024 11:08:08 +0200 Subject: [PATCH 29/43] RUM-4079 Migrate dogfooding automation to GitLab --- .gitignore | 1 + .gitlab-ci.yml | 23 ++ Makefile | 13 + bitrise.yml | 28 +- tools/distribution/dogfood.py | 150 ---------- tools/distribution/requirements.txt | 5 - .../src/dogfood/dogfooded_commit.py | 37 --- tools/distribution/src/dogfood/repository.py | 84 ------ tools/distribution/src/utils.py | 93 ------ tools/{distribution => dogfooding}/Makefile | 7 +- tools/dogfooding/dogfood.sh | 271 ++++++++++++++++++ tools/dogfooding/read-dogfooded-hash.py | 44 +++ tools/dogfooding/requirements.txt | 8 + .../src/dogfood/package_resolved.py | 23 +- tools/dogfooding/src/utils.py | 20 ++ .../tests/__init__.py | 0 .../tests/dogfood/__init__.py | 0 .../tests/dogfood/test_package_resolved.py | 0 tools/dogfooding/update-dependency.py | 81 ++++++ tools/license/check-license.sh | 2 +- tools/tools-test.sh | 6 +- tools/utils/common.mk | 1 + tools/utils/current-git.sh | 21 +- 23 files changed, 506 insertions(+), 412 deletions(-) delete mode 100755 tools/distribution/dogfood.py delete mode 100644 tools/distribution/requirements.txt delete mode 100644 tools/distribution/src/dogfood/dogfooded_commit.py delete mode 100644 tools/distribution/src/dogfood/repository.py delete mode 100644 tools/distribution/src/utils.py rename tools/{distribution => dogfooding}/Makefile (69%) create mode 100755 tools/dogfooding/dogfood.sh create mode 100644 tools/dogfooding/read-dogfooded-hash.py create mode 100644 tools/dogfooding/requirements.txt rename tools/{distribution => dogfooding}/src/dogfood/package_resolved.py (94%) create mode 100644 tools/dogfooding/src/utils.py rename tools/{distribution => dogfooding}/tests/__init__.py (100%) rename tools/{distribution => dogfooding}/tests/dogfood/__init__.py (100%) rename tools/{distribution => dogfooding}/tests/dogfood/test_package_resolved.py (100%) create mode 100644 tools/dogfooding/update-dependency.py diff --git a/.gitignore b/.gitignore index c30e539da4..896abc144a 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ Carthage/Checkouts xcuserdata/ *.local.xcconfig E2ETests/code-signing +tools/dogfooding/repos # Ignore files for Python tools: .idea diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c025e1736c..82a7265346 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -5,6 +5,7 @@ stages: - ui-test - smoke-test - e2e-test + - dogfood - release-build - release-publish @@ -268,6 +269,28 @@ E2E Test (upload to s8s): - export DRY_RUN=${DRY_RUN:-0} # default to 0 if not specified - make e2e-build-upload ARTIFACTS_PATH="artifacts/e2e" +# ┌─────────────────┐ +# │ SDK dogfooding: │ +# └─────────────────┘ + +Dogfood (Shopist): + stage: dogfood + rules: + - if: '$CI_COMMIT_BRANCH == $DEVELOP_BRANCH' + when: manual + script: + - ./tools/runner-setup.sh --ssh # temporary, waiting for AMI + - DRY_RUN=0 make dogfood-shopist + +Dogfood (Datadog app): + stage: dogfood + rules: + - if: '$CI_COMMIT_BRANCH == $DEVELOP_BRANCH' + when: manual + script: + - ./tools/runner-setup.sh --ssh # temporary, waiting for AMI + - DRY_RUN=0 make dogfood-datadog-app + # ┌──────────────┐ # │ SDK release: │ # └──────────────┘ diff --git a/Makefile b/Makefile index e9f782dff4..5105b5cb58 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,7 @@ all: env-check repo-setup templates spm-build spm-build-ios spm-build-tvos spm-build-visionos spm-build-macos spm-build-watchos \ e2e-build-upload \ models-generate rum-models-generate sr-models-generate models-verify rum-models-verify sr-models-verify \ + dogfood-shopist dogfood-datadog-app \ release-build release-validate release-publish-github \ release-publish-podspec release-publish-internal-podspecs release-publish-dependent-podspecs release-publish-legacy-podspecs \ set-ci-secret @@ -367,6 +368,18 @@ e2e-monitors-generate: @./tools/nightly-e2e-tests/nightly_e2e.py generate-tf --tests-dir ../../Datadog/E2ETests @echo "⚠️ Remember to delete all iOS monitors manually from Mobile-Integration org before running 'terraform apply'." +# Creates dogfooding PR in shopist-ios +dogfood-shopist: + @:$(eval DRY_RUN ?= 1) + @$(ECHO_TITLE) "make dogfood-shopist DRY_RUN='$(DRY_RUN)'" + DRY_RUN=$(DRY_RUN) ./tools/dogfooding/dogfood.sh --shopist + +# Creates dogfooding PR in datadog-ios +dogfood-datadog-app: + @:$(eval DRY_RUN ?= 1) + @$(ECHO_TITLE) "make dogfood-datadog-app DRY_RUN='$(DRY_RUN)'" + DRY_RUN=$(DRY_RUN) ./tools/dogfooding/dogfood.sh --datadog-app + # Builds release artifacts for given tag release-build: @$(call require_param,GIT_TAG) diff --git a/bitrise.yml b/bitrise.yml index 7af92f18c2..7e61fca314 100644 --- a/bitrise.yml +++ b/bitrise.yml @@ -15,14 +15,16 @@ workflows: push_to_develop_or_master: description: |- This workflow is triggered for each new commit pushed to `develop` or `master` branch. - after_run: - - _make_dependencies - - _deploy_artifacts + steps: + - script: + inputs: + - content: echo "NOP" push_to_dogfooding: - after_run: - - create_dogfooding_pr - - _notify_failure_on_slack + steps: + - script: + inputs: + - content: echo "NOP" run_nightly_e2e_tests: after_run: @@ -75,20 +77,6 @@ workflows: - icon_url: 'https://avatars.githubusercontent.com/t/3555052?s=128&v=4' - webhook_url: '${SLACK_INCOMING_WEBHOOK_MOBILE_CI}' - create_dogfooding_pr: - description: |- - Creates PRs to repositories using `dd-sdk-ios`. - steps: - - script: - title: Create PR to Datadog mobile app project - inputs: - - content: |- - #!/usr/bin/env zsh - set -e - - cd tools/distribution && make clean install - venv/bin/python3 dogfood.py - run_e2e_tests: description: |- Runs E2E tests on iOS Simulator. diff --git a/tools/distribution/dogfood.py b/tools/distribution/dogfood.py deleted file mode 100755 index 1a9eb9dcf1..0000000000 --- a/tools/distribution/dogfood.py +++ /dev/null @@ -1,150 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import sys -import os -import traceback -from tempfile import TemporaryDirectory -from src.dogfood.package_resolved import PackageResolvedFile, PackageID -from src.dogfood.dogfooded_commit import DogfoodedCommit -from src.dogfood.repository import Repository -from src.utils import remember_cwd - - -def dogfood(dry_run: bool, repository_url: str, repository_name: str, repository_package_resolved_paths: [str]): - print(f'🐶 Dogfooding: {repository_name}...') - - # Read commit information: - dd_sdk_ios_commit = DogfoodedCommit() - - # Resolve and read `dd-sdk-ios` dependencies: - dd_sdk_package_path = '../..' - os.system(f'swift package --package-path {dd_sdk_package_path} resolve') - dd_sdk_ios_package = PackageResolvedFile(path=f'{dd_sdk_package_path}/Package.resolved') - dd_sdk_ios_package.print() - - if dd_sdk_ios_package.version > 3: - raise Exception( - f'`dogfood.py` expects the `package.resolved` in `dd-sdk-ios` to use version <= 3 ' + - f'but version {dd_sdk_ios_package.version} was detected. Update `dogfood.py` to use this version.' - ) - - # Clone dependant repo to temporary location and update its `Package.resolved` (one or many) so it points - # to the current `dd-sdk-ios` commit. After that, push changes to dependant repo and create dogfooding PR. - with TemporaryDirectory() as temp_dir: - with remember_cwd(): - repository = Repository.clone( - ssh=repository_url, - repository_name=repository_name, - temp_dir=temp_dir - ) - repository.create_branch(f'dogfooding-{dd_sdk_ios_commit.hash_short}') - - packages: [PackageResolvedFile] = list( - map(lambda path: PackageResolvedFile(path=path), repository_package_resolved_paths) - ) - - for package in packages: - # Update version of `dd-sdk-ios`: - package.update_dependency( - package_id=PackageID(v1='DatadogSDK', v2='dd-sdk-ios'), - new_branch='dogfooding', - new_revision=dd_sdk_ios_commit.hash, - new_version=None - ) - - # Add or update `dd-sdk-ios` dependencies: - for dependency_id in dd_sdk_ios_package.read_dependency_ids(): - dependency = dd_sdk_ios_package.read_dependency(package_id=dependency_id) - - if package.has_dependency(package_id=dependency_id): - package.update_dependency( - package_id=dependency_id, - new_branch=dependency['state'].get('branch'), - new_revision=dependency['state']['revision'], - new_version=dependency['state'].get('version'), - ) - else: - package.add_dependency( - package_id=dependency_id, - repository_url=dependency['location'], - branch=dependency['state'].get('branch'), - revision=dependency['state']['revision'], - version=dependency['state'].get('version'), - ) - - package.save() - - # Push changes to dependant repo: - repository.commit( - message=f'Dogfooding dd-sdk-ios commit: {dd_sdk_ios_commit.hash}\n\n' + - f'Dogfooded commit message: {dd_sdk_ios_commit.message}', - author=dd_sdk_ios_commit.author - ) - - if dry_run: - package.print() - else: - repository.push() - # Create PR: - repository.create_pr( - title=f'[Dogfooding] Upgrade dd-sdk-ios to {dd_sdk_ios_commit.hash_short}', - description='⚙️ This is an automated PR upgrading the version of \`dd-sdk-ios\` to ' + - f'https://github.com/DataDog/dd-sdk-ios/commit/{dd_sdk_ios_commit.hash}' - ) - - -if __name__ == "__main__": - # Change working directory to `tools/distribution/` - print(f'ℹ️ Launch dir: {sys.argv[0]}') - launch_dir = os.path.dirname(sys.argv[0]) - launch_dir = '.' if launch_dir == '' else launch_dir - if launch_dir == 'tools/distribution': - print(f' → changing current directory to: {os.getcwd()}') - os.chdir('tools/distribution') - - try: - dry_run = os.environ.get('DD_DRY_RUN') == 'yes' - if dry_run: - print(f'ℹ️ Running in dry-run mode') - skip_datadog_ios = os.environ.get('DD_SKIP_DATADOG_IOS') == 'yes' - skip_shopist_ios = os.environ.get('DD_SKIP_SHOPIST_IOS') == 'yes' - - # Dogfood in Datadog iOS app - if not skip_datadog_ios: - dogfood( - dry_run=dry_run, - repository_url='git@github.com:DataDog/datadog-ios.git', - repository_name='datadog-ios', - repository_package_resolved_paths=[ - '.package.resolved', - 'DatadogApp.xcworkspace/xcshareddata/swiftpm/Package.resolved' - ] - ) - - # Dogfood in Shopist iOS - if not skip_shopist_ios: - dogfood( - dry_run=dry_run, - repository_url='git@github.com:DataDog/shopist-ios.git', - repository_name='shopist-ios', - repository_package_resolved_paths=[ - 'Shopist/Shopist.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved' - ] - ) - - except Exception as error: - print(f'❌ Failed to dogfood: {error}') - print('-' * 60) - traceback.print_exc(file=sys.stdout) - print('-' * 60) - sys.exit(1) - - sys.exit(0) - diff --git a/tools/distribution/requirements.txt b/tools/distribution/requirements.txt deleted file mode 100644 index 884b308d41..0000000000 --- a/tools/distribution/requirements.txt +++ /dev/null @@ -1,5 +0,0 @@ -gitdb==4.0.10 -GitPython==3.1.41 -smmap==5.0.0 -packaging==23.1 -pytest==7.4.0 diff --git a/tools/distribution/src/dogfood/dogfooded_commit.py b/tools/distribution/src/dogfood/dogfooded_commit.py deleted file mode 100644 index 5af5148cf9..0000000000 --- a/tools/distribution/src/dogfood/dogfooded_commit.py +++ /dev/null @@ -1,37 +0,0 @@ -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import os -from git import Repo, Actor - - -class DogfoodedCommit: - """ - Reads the git information for the commit being dogfooded. - * when running on CI - reads it from CI ENV - * when running locally - reads it from `dd-sdk-ios` repo git info - """ - - def __init__(self): - if os.environ.get('CI') == 'true': - print('ℹ️ Running on CI') - print(' → reading git author from ENV') - self.author = Actor( - name=os.environ['GIT_CLONE_COMMIT_AUTHOR_NAME'], - email=os.environ['GIT_CLONE_COMMIT_AUTHOR_EMAIL'] - ) - self.hash = os.environ['GIT_CLONE_COMMIT_HASH'] - self.message = os.environ['GIT_CLONE_COMMIT_MESSAGE_SUBJECT'] - else: - print('ℹ️ Running locally') - print(' → reading git author from the last commit in dd-sdk-ios') - dd_sdk_ios_repo = Repo(path='../../') - self.author = dd_sdk_ios_repo.head.commit.author - self.hash = dd_sdk_ios_repo.head.commit.hexsha - self.message = dd_sdk_ios_repo.head.commit.message - - self.hash_short = self.hash[0:8] - print(f' → Read git info (author: "{self.author}", hash: "{self.hash}", message: "{self.message}")') diff --git a/tools/distribution/src/dogfood/repository.py b/tools/distribution/src/dogfood/repository.py deleted file mode 100644 index 933d76deb8..0000000000 --- a/tools/distribution/src/dogfood/repository.py +++ /dev/null @@ -1,84 +0,0 @@ -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - - -import os -from typing import Union -from git import Repo, Actor - - -default_git_author = Actor(name='mobile-app-ci', email='') - - -class Repository: - """ - Abstracts operations on the target repository. - """ - - def __init__(self, repo: Repo): - self.repo = repo - - @staticmethod - def clone(ssh: str, repository_name: str, temp_dir: str): - """ - Clones the git repository using GH CLI. - The GH CLI must be authentication by the environment. - :returns git.Repo object - """ - print('ℹ️️ Logging GH CLI authentication status:') - os.system('gh auth status') - - print(f'ℹ️️ Changing current directory to: {temp_dir}') - os.chdir(temp_dir) - - print(f'⚙️ Cloning {ssh}') - result = os.system(f'gh repo clone {ssh}') - - if result > 0: - raise Exception( - f'Unable to clone GH repository ({ssh}). Check GH CLI authentication status in above logs.' - ) - else: - print(f' → changing current directory to: {os.getcwd()}/{repository_name}') - os.chdir(repository_name) - return Repository(repo=Repo(path=os.getcwd())) - - def create_branch(self, branch_name): - """ - Creates and checks out a git branch. - :param branch_name: the name of the branch - """ - print(f'⚙️️️️ Creating git branch: {branch_name}') - self.repo.git.checkout('HEAD', b=branch_name) - - def commit(self, message: str, author: Union[None, 'Actor'] = None): - """ - Creates commit with current changes. - :param message: commit message - :param author: author of the commit (git.Actor object) or None (will be read from git config) - """ - if author: - print(f'⚙️️️️ Committing changes on behalf of {author.name} ({author.email})') - else: - print(f'⚙️️️️ Committing changes using git user from current git config') - print(' → commit message:') - print(message) - self.repo.git.add(update=True) - self.repo.index.commit(message=message, author=author, committer=author) - - def push(self): - """ - Pushes current branch to the remote. - """ - print(f'⚙️️️️ Pushing to remote') - origin = self.repo.remote(name="origin") - self.repo.git.push("--set-upstream", "--force", origin, self.repo.head.ref) - - def create_pr(self, title: str, description: str): - print(f'⚙️️ Creating draft PR') - print(f' → title: {title}') - print(f' → description: {description}') - os.system(f'gh pr create --title "{title}" --body "{description}" --draft') diff --git a/tools/distribution/src/utils.py b/tools/distribution/src/utils.py deleted file mode 100644 index 042b3996a7..0000000000 --- a/tools/distribution/src/utils.py +++ /dev/null @@ -1,93 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import re -import os -import subprocess -import contextlib - -@contextlib.contextmanager -def remember_cwd(): - """ - Creates context manager for convenient work with `os.chdir()` API. - After context returns, the `os.getcwd()` is set to its previous value. - """ - previous = os.getcwd() - try: - yield - finally: - os.chdir(previous) - - -def shell(command: str, skip: bool = False): - """ - Executes given shell command without capturing its output. - Fails on exit code != 0. - """ - print(f' → running `{command}`' if not skip else f' → running `{command}` - ⚡️ skipped (dry_run)') - if not skip: - result = os.system(command) - else: - result = 0 - - if result != 0: - raise Exception(f'Failed on: `{command}` with exit code {result}') - - -# Copied from `tools/nightly-unit-tests/src/utils.py` -# TODO: RUMM-1860 Share this code between both tools -def shell_output(command: str): - """ - Runs shell command and returns its output. - Fails on exit code != 0. - """ - process = subprocess.run( - args=[command], - capture_output=True, - shell=True, - text=True # capture STDOUT as text - ) - if process.returncode == 0: - return process.stdout - else: - raise Exception( - f''' - Command {command} exited with status code {process.returncode} - - STDOUT: {process.stdout if process.stdout != '' else '""'} - - STDERR: {process.stderr if process.stderr != '' else '""'} - ''' - ) - - -def read_sdk_version() -> str: - """ - Reads SDK version from 'Sources/Datadog/Versioning.swift'. - """ - file = 'DatadogCore/Sources/Versioning.swift' - regex = r'^internal let __sdkVersion = \"(.*)?\"$' - - with open(file) as version_file: - for line in version_file.readlines(): - if match := re.match(regex, line): - return match.group(1) - - raise Exception(f'Expected `__sdkVersion` not found in {file}') - - -def read_xcode_version() -> str: - """ - Reads Xcode version from `xcodebuild -version`. Returns only the version number, e.g. '13.2.1' - """ - xc_version_regex = r'^Xcode (.+)\n' # e.g. 'Xcode 13.1', 'Xcode 13.2 Beta 2' - xc_version_string = shell_output(command='xcodebuild -version') - - if match := re.match(xc_version_regex, xc_version_string): - return match.groups()[0] - else: - raise Exception(f'Cannot read Xcode version from `xcodebuild -version` output: {xc_version_string}') diff --git a/tools/distribution/Makefile b/tools/dogfooding/Makefile similarity index 69% rename from tools/distribution/Makefile rename to tools/dogfooding/Makefile index 0af737fb91..fde806cdae 100644 --- a/tools/distribution/Makefile +++ b/tools/dogfooding/Makefile @@ -1,4 +1,4 @@ -.PHONY: clean install test +.PHONY: clean install run test REPO_ROOT := ../../ include ../utils/common.mk @@ -12,6 +12,11 @@ install: python3 -m venv venv venv/bin/pip3 install -r requirements.txt +run: + @$(call require_param,PARAMS) + @$(ECHO_SUBTITLE2) "make run" + venv/bin/python3 $(PARAMS) + test: @$(ECHO_SUBTITLE2) "make test" venv/bin/python3 -m pytest tests diff --git a/tools/dogfooding/dogfood.sh b/tools/dogfooding/dogfood.sh new file mode 100755 index 0000000000..d13c97c746 --- /dev/null +++ b/tools/dogfooding/dogfood.sh @@ -0,0 +1,271 @@ +#!/bin/zsh + +# Usage: +# $ ./tools/dogfooding/dogfood.sh -h +# Updates the 'dd-sdk-ios' version in a dependent project and creates a dogfooding PR in its repository. + +# Options: +# --shopist Dogfood in the Shopist iOS project. +# --datadog-app Dogfood in the Datadog iOS app. + +set -eo pipefail +source ./tools/utils/argparse.sh +source ./tools/utils/echo-color.sh +source ./tools/utils/current-git.sh +source ./tools/secrets/get-secret.sh + +set_description "Updates 'dd-sdk-ios' version in dependant project and opens dogfooding PR to its repo." +define_arg "shopist" "false" "Dogfood in Shopist iOS." "store_true" +define_arg "datadog-app" "false" "Dogfood in Datadog iOS app." "store_true" + +check_for_help "$@" +parse_args "$@" + +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" +echo_info "cd '$SCRIPT_DIR'" && cd "$SCRIPT_DIR" + +DEPENDANT_REPO_CLONE_DIR="repos" + +SDK_PACKAGE_PATH=$(realpath "../..") +SDK_VERSION_FILE_PATH=$(realpath "../../DatadogCore/Sources/Versioning.swift") +DOGFOODED_BRANCH="$(current_git_branch)" +DOGFOODED_COMMIT="$(current_git_commit)" +DOGFOODED_COMMIT_SHORT="$(current_git_commit_short)" +DOGFOODING_BRANCH_NAME="dogfooding-$DOGFOODED_COMMIT_SHORT" # the name of the branch to create in dependant repo + +if [[ "$DOGFOODED_BRANCH" != "develop" ]]; then + DRY_RUN=1 + echo_warn "DOGFOODED_BRANCH is not 'develop'. Enforcing DRY_RUN=1." +fi + +echo_info "▸ PWD = '$(pwd)'" +echo_info "▸ DRY_RUN = '$DRY_RUN'" +echo_info "▸ SDK_PACKAGE_PATH = '$SDK_PACKAGE_PATH'" +echo_info "▸ SDK_VERSION_FILE_PATH = '$SDK_VERSION_FILE_PATH'" +echo_info "▸ DOGFOODED_BRANCH = '$DOGFOODED_BRANCH'" +echo_info "▸ DOGFOODED_COMMIT = '$DOGFOODED_COMMIT'" +echo_info "▸ DOGFOODED_COMMIT_SHORT = '$DOGFOODED_COMMIT_SHORT'" +echo_info "▸ DOGFOODING_BRANCH_NAME = '$DOGFOODING_BRANCH_NAME'" + +# Prepares dogfooding package. +prepare() { + echo_subtitle "Prepare dogfooding package" + rm -rf "$DEPENDANT_REPO_CLONE_DIR" + mkdir -p "$DEPENDANT_REPO_CLONE_DIR" + make clean install +} + +# Cleans up dogfooding package. +cleanup() { + echo_subtitle "Clean dogfooding package" + make clean + rm -rf "$DEPENDANT_REPO_CLONE_DIR" +} + +# Clones dependant repo. +clone_repo() { + local ssh="$1" + local branch="$2" + local clone_path="$3" + echo_subtitle "Clone '$REPO_NAME' repo (branch: '$branch')" + git clone --branch $branch --single-branch $1 $clone_path +} + +# Creates dogfooding commit in dependant repo. +commit_repo() { + local repo_path="$1" + echo_subtitle "Commit '$REPO_NAME' repo" + cd "$repo_path" + git checkout -b "$DOGFOODING_BRANCH_NAME" + git add . + git commit -m "Dogfooding dd-sdk-ios commit: $DOGFOODED_COMMIT" + cd - +} + +# Pushes dogfooding branch to dependant repo. +push_repo() { + local repo_path="$1" + echo_subtitle "Push '$DOGFOODING_BRANCH_NAME' to '$REPO_NAME' repo" + cd "$repo_path" + if [ "$DRY_RUN" = "1" ] || [ "$DRY_RUN" = "true" ]; then + echo_warn "Running in DRY RUN mode. Skipping 'git push'." + else + git push -u origin "$DOGFOODING_BRANCH_NAME" --force + fi + cd - +} + +# Creates dogfooding PR in dependant repo. +create_pr() { + local repo_path="$1" + local changelog="$2" + local target_branch="$3" + echo_subtitle "Create PR in '$REPO_NAME' repo" + echo_info "▸ Exporting 'GITHUB_TOKEN' for CI" + export GITHUB_TOKEN=$(get_secret $DD_IOS_SECRET__GH_CLI_TOKEN) + echo_info "▸ gh auth status" + gh auth status + if [[ $? -ne 0 ]]; then + echo_err "Error:" "GitHub CLI is not authenticated." + exit 1 + fi + + PR_TITLE="[Dogfooding] Upgrade dd-sdk-ios to \`$DOGFOODED_SDK_VERSION\`" + PR_DESCRIPTION="$(cat <&2 + + if [ "$CI" = "true" ]; then + # Fetch branch history and unshallow in GitLab which only does shallow clone by default + echo_info "▸ Fetching git history " >&2 + git fetch -q --unshallow >&2 + fi + + # Read git history from last dogfooded commit to current: + echo_info "▸ Reading commits ($LAST_DOGFOODED_COMMIT..HEAD):" >&2 + git_log=$(git --no-pager log \ + --pretty=oneline "$LAST_DOGFOODED_COMMIT..HEAD" \ + --ancestry-path "origin/$DOGFOODED_BRANCH" + ) + echo_info ">>> git log begin" >&2 + echo "$git_log" >&2 + echo_info "<<< git log end" >&2 + + # Extract only merge commits: + CHANGELOG=$(echo "$git_log" | grep -o 'Merge pull request #[0-9]\+' | awk -F'#' '{print "- https://github.com/DataDog/dd-sdk-ios/pull/"$2}' || true) + if [ -z "$CHANGELOG" ]; then + CHANGELOG="- Empty (no PRs merged since https://github.com/DataDog/dd-sdk-ios/commit/$LAST_DOGFOODED_COMMIT)" + fi + + echo_info "▸ Changelog:" >&2 + echo_info ">>> changelog begin" >&2 + echo_succ "$CHANGELOG" >&2 + echo_info "<<< changelog end" >&2 + + echo "$CHANGELOG" +} + +# Updates dd-sdk-ios version in dependant project to DOGFOODED_COMMIT. +update_dependant_package_resolved() { + local package_resolved_path="$1" + echo_subtitle "Update dd-sdk-ios version in '$package_resolved_path'" + make run PARAMS="update-dependency.py \ + --repo-package-resolved-path '$package_resolved_path' \ + --dogfooded-package-resolved-path '$SDK_PACKAGE_PATH/Package.resolved' \ + --dogfooded-branch '$DOGFOODED_BRANCH' \ + --dogfooded-commit '$DOGFOODED_COMMIT'" +} + +# Updates 'sdk_version' in dependant project to DOGFOODED_SDK_VERSION. +update_dependant_sdk_version() { + local version_file="$1" + echo_subtitle "Update 'sdk_version' in '$version_file'" + + echo_info ">>> '$version_file' before" + cat "$version_file" + echo_info "<<< '$version_file' before" + + # sed -i '' "s/internal let __dogfoodedSDKVersion = \".*\"/internal let __dogfoodedSDKVersion = \"$DOGFOODED_SDK_VERSION\"/" "$version_file" + sed -i '' -E "s/(let __dogfoodedSDKVersion = \")[^\"]*(\")/\1${DOGFOODED_SDK_VERSION}\2/" "$version_file" + echo_succ "▸ Updated '$version_file' to:" + + echo_info ">>> '$version_file' after" + cat "$version_file" + echo_info "<<< '$version_file' after" +} + +prepare +trap "cleanup" EXIT INT + +read_dogfooded_version +resolve_dd_sdk_ios_package + +if [ "$shopist" = "true" ]; then + REPO_NAME="shopist-ios" + CLONE_PATH="$DEPENDANT_REPO_CLONE_DIR/$REPO_NAME" + DEFAULT_BRANCH="main" + + clone_repo "git@github.com:DataDog/shopist-ios.git" $DEFAULT_BRANCH $CLONE_PATH + + # Generate CHANGELOG: + read_last_dogfooded_commit "$CLONE_PATH/Shopist/Shopist.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" + CHANGELOG=$(print_changelog_since_last_dogfooded_commit) + + # Update dd-sdk-ios version: + update_dependant_package_resolved "$CLONE_PATH/Shopist/Shopist.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" + update_dependant_sdk_version "$CLONE_PATH/Shopist/Shopist/DogfoodingConfig.swift" + + # Push & create PR: + commit_repo $CLONE_PATH + push_repo $CLONE_PATH + create_pr $CLONE_PATH $CHANGELOG $DEFAULT_BRANCH +fi + +if [ "$datadog_app" = "true" ]; then + REPO_NAME="datadog-ios" + CLONE_PATH="$DEPENDANT_REPO_CLONE_DIR/$REPO_NAME" + DEFAULT_BRANCH="develop" + + clone_repo "git@github.com:DataDog/datadog-ios.git" $DEFAULT_BRANCH $CLONE_PATH + + # Generate CHANGELOG: + read_last_dogfooded_commit "$CLONE_PATH/DatadogApp.xcworkspace/xcshareddata/swiftpm/Package.resolved" + CHANGELOG=$(print_changelog_since_last_dogfooded_commit) + + # Update dd-sdk-ios version: + update_dependant_package_resolved "$CLONE_PATH/DatadogApp.xcworkspace/xcshareddata/swiftpm/Package.resolved" + update_dependant_package_resolved "$CLONE_PATH/.package.resolved" + update_dependant_sdk_version "$CLONE_PATH/Targets/DogLogger/Datadog/DogfoodingConfig.swift" + + # Push & create PR: + commit_repo $CLONE_PATH + push_repo $CLONE_PATH + create_pr $CLONE_PATH $CHANGELOG $DEFAULT_BRANCH +fi diff --git a/tools/dogfooding/read-dogfooded-hash.py b/tools/dogfooding/read-dogfooded-hash.py new file mode 100644 index 0000000000..3ade98d56e --- /dev/null +++ b/tools/dogfooding/read-dogfooded-hash.py @@ -0,0 +1,44 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ----------------------------------------------------------- +# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019-Present Datadog, Inc. +# ----------------------------------------------------------- + +import sys +import traceback +import argparse +from src.dogfood.package_resolved import PackageResolvedFile, PackageID +from src.utils import print_err, print_info + +def print_to_stdout(hash): + """ + Prints the hash in a format that is recognized by caller script. + """ + print(f'DOGFOODED_HASH={hash}') + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Reads the hash of dd-sdk-ios dependency in "Package.resolved" of SDK-dependant project and prints it to STDOUT.') + parser.add_argument('--repo-package-resolved-path', type=str, required=True, help='Path to "Package.resolved" file in SDK-dependant project') + args = parser.parse_args() + + try: + dependant_package = PackageResolvedFile(path=args.repo_package_resolved_path) + dependency = dependant_package.read_dependency(package_id=PackageID(v1='DatadogSDK', v2='dd-sdk-ios')) + print_info(f"▸ Found dd-sdk-ios dependency in '{args.repo_package_resolved_path}': {dependency}") + hash=dependency['state'].get('revision') + + if not hash: + raise Exception(f'Dogfooded dependency is missing hash: {dependency}') + + print_to_stdout(hash=hash) + except Exception as error: + print_err(f'Failed to get last dependency hash: {error}') + print('-' * 60) + traceback.print_exc(file=sys.stdout) + print('-' * 60) + sys.exit(1) + + sys.exit(0) \ No newline at end of file diff --git a/tools/dogfooding/requirements.txt b/tools/dogfooding/requirements.txt new file mode 100644 index 0000000000..e1fe3af2c4 --- /dev/null +++ b/tools/dogfooding/requirements.txt @@ -0,0 +1,8 @@ +exceptiongroup==1.2.2 +gitdb==4.0.10 +iniconfig==2.0.0 +packaging==23.1 +pluggy==1.5.0 +pytest==7.4.0 +smmap==5.0.0 +tomli==2.0.1 diff --git a/tools/distribution/src/dogfood/package_resolved.py b/tools/dogfooding/src/dogfood/package_resolved.py similarity index 94% rename from tools/distribution/src/dogfood/package_resolved.py rename to tools/dogfooding/src/dogfood/package_resolved.py index 225e452ce9..455f192532 100644 --- a/tools/distribution/src/dogfood/package_resolved.py +++ b/tools/dogfooding/src/dogfood/package_resolved.py @@ -8,6 +8,7 @@ from dataclasses import dataclass from copy import deepcopy from typing import Optional +from src.utils import print_info, print_succ @dataclass() @@ -79,7 +80,7 @@ class PackageResolvedFile(PackageResolvedContent): wrapped: PackageResolvedContent def __init__(self, path: str): - print(f'⚙️ Opening {path}') + print_info(f'▸ Opening {path}') self.path = path with open(path, 'r') as file: self.packages = json.load(file) @@ -100,7 +101,7 @@ def save(self): """ Saves changes to initial `path`. """ - print(f'⚙️ Saving {self.path}') + print_info(f'▸ Saving {self.path}') with open(self.path, 'w') as file: json.dump( self.packages, @@ -116,7 +117,7 @@ def print(self): Prints the content of this file. """ with open(self.path, 'r') as file: - print(f'⚙️ Content of {file.name}:') + print_info(f'▸ Content of {file.name}:') print(file.read()) def has_dependency(self, package_id: PackageID) -> bool: @@ -176,7 +177,7 @@ def has_dependency(self, package_id: PackageID): return package_id.v1 in [p['package'] for p in pins] def update_dependency(self, package_id: PackageID, new_branch: Optional[str], new_revision: str, new_version: Optional[str]): - print(f'⚙️ ️ Updating "{package_id.v1}" in {self.path} (V1):') + print_info(f'▸ Updating "{package_id.v1}" in {self.path} (V1):') package = self.__get_package(package_id=package_id) old_state = deepcopy(package['state']) @@ -190,11 +191,11 @@ def update_dependency(self, package_id: PackageID, new_branch: Optional[str], ne diff = old_state.items() ^ new_state.items() if len(diff) > 0: - print(f'✏️️ Updated "{package_id.v1}" in {self.path}:') + print_info(f'▸ Updated "{package_id.v1}" in {self.path}:') print(f' → old: {old_state}') print(f' → new: {new_state}') else: - print(f'✏️️ "{package_id.v1}" is up-to-date in {self.path}') + print_succ(f'▸ "{package_id.v1}" is up-to-date in {self.path}') def add_dependency(self, package_id: PackageID, repository_url: str, branch: Optional[str], revision: str, version: Optional[str]): pins = self.packages['object']['pins'] @@ -216,7 +217,7 @@ def add_dependency(self, package_id: PackageID, repository_url: str, branch: Opt pins.insert(index, new_pin) - print(f'✏️️ Added "{package_id.v1}" at index {index} in {self.path}:') + print_info(f'▸ Added "{package_id.v1}" at index {index} in {self.path}:') print(f' → branch: {branch}') print(f' → revision: {revision}') print(f' → version: {version}') @@ -276,7 +277,7 @@ def has_dependency(self, package_id: PackageID): return package_id.v2 in [p['identity'] for p in pins] def update_dependency(self, package_id: PackageID, new_branch: Optional[str], new_revision: str, new_version: Optional[str]): - print(f'⚙️ ️ Updating "{package_id.v2}" in {self.path} (V2):') + print_info(f'▸ Updating "{package_id.v2}" in {self.path} (V2):') package = self.__get_package(package_id=package_id) old_state = deepcopy(package['state']) @@ -301,11 +302,11 @@ def update_dependency(self, package_id: PackageID, new_branch: Optional[str], ne diff = old_state.items() ^ new_state.items() if len(diff) > 0: - print(f'✏️️ Updated "{package_id.v2}" in {self.path}:') + print_info(f'▸ Updated "{package_id.v2}" in {self.path}:') print(f' → old: {old_state}') print(f' → new: {new_state}') else: - print(f'✏️️ "{package_id.v2}" is up-to-date in {self.path}') + print_succ(f'▸ "{package_id.v2}" is up-to-date in {self.path}') def add_dependency(self, package_id: PackageID, repository_url: str, branch: Optional[str], revision: str, version: Optional[str]): pins = self.packages['pins'] @@ -332,7 +333,7 @@ def add_dependency(self, package_id: PackageID, repository_url: str, branch: Opt pins.insert(index, new_pin) - print(f'✏️️ Added "{package_id.v2}" at index {index} in {self.path}:') + print_info(f'▸ Added "{package_id.v2}" at index {index} in {self.path}:') print(f' → branch: {branch}') print(f' → revision: {revision}') print(f' → version: {version}') diff --git a/tools/dogfooding/src/utils.py b/tools/dogfooding/src/utils.py new file mode 100644 index 0000000000..d7aa55f0ba --- /dev/null +++ b/tools/dogfooding/src/utils.py @@ -0,0 +1,20 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ----------------------------------------------------------- +# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019-Present Datadog, Inc. +# ----------------------------------------------------------- + +def print_err(message): + print(f"\033[91m{message}\033[0m") + +def print_warn(message): + print(f"\033[93m{message}\033[0m") + +def print_succ(message): + print(f"\033[92m{message}\033[0m") + +def print_info(message): + print(f"\033[94m{message}\033[0m") diff --git a/tools/distribution/tests/__init__.py b/tools/dogfooding/tests/__init__.py similarity index 100% rename from tools/distribution/tests/__init__.py rename to tools/dogfooding/tests/__init__.py diff --git a/tools/distribution/tests/dogfood/__init__.py b/tools/dogfooding/tests/dogfood/__init__.py similarity index 100% rename from tools/distribution/tests/dogfood/__init__.py rename to tools/dogfooding/tests/dogfood/__init__.py diff --git a/tools/distribution/tests/dogfood/test_package_resolved.py b/tools/dogfooding/tests/dogfood/test_package_resolved.py similarity index 100% rename from tools/distribution/tests/dogfood/test_package_resolved.py rename to tools/dogfooding/tests/dogfood/test_package_resolved.py diff --git a/tools/dogfooding/update-dependency.py b/tools/dogfooding/update-dependency.py new file mode 100644 index 0000000000..141a3ce8ab --- /dev/null +++ b/tools/dogfooding/update-dependency.py @@ -0,0 +1,81 @@ +#!/usr/bin/env python3 +# -*- coding: utf-8 -*- + +# ----------------------------------------------------------- +# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. +# This product includes software developed at Datadog (https://www.datadoghq.com/). +# Copyright 2019-Present Datadog, Inc. +# ----------------------------------------------------------- + +import sys +import traceback +import argparse +from src.dogfood.package_resolved import PackageResolvedFile, PackageID +from src.utils import print_succ, print_err + +def dogfood(args): + # Read dd-sdk-ios `Package.resolved`` + dd_sdk_ios_package = PackageResolvedFile(path=args.dogfooded_package_resolved_path) + dd_sdk_ios_package.print() + + if dd_sdk_ios_package.version > 3: + raise Exception( + f'The `{dd_sdk_ios_package.path}` uses version ({dd_sdk_ios_package.version}) not supported by dogfooding automation.' + ) + + # Read dependant `Package.resolved` + dependant_package = PackageResolvedFile(path=args.repo_package_resolved_path) + + # Update version of `dd-sdk-ios`: + dependant_package.update_dependency( + package_id=PackageID(v1='DatadogSDK', v2='dd-sdk-ios'), + new_branch=args.dogfooded_branch, + new_revision=args.dogfooded_commit, + new_version=None + ) + + # Add or update `dd-sdk-ios` dependencies: + for dependency_id in dd_sdk_ios_package.read_dependency_ids(): + dependency = dd_sdk_ios_package.read_dependency(package_id=dependency_id) + + if dependant_package.has_dependency(package_id=dependency_id): + dependant_package.update_dependency( + package_id=dependency_id, + new_branch=dependency['state'].get('branch'), + new_revision=dependency['state']['revision'], + new_version=dependency['state'].get('version'), + ) + else: + dependant_package.add_dependency( + package_id=dependency_id, + repository_url=dependency['location'], + branch=dependency['state'].get('branch'), + revision=dependency['state']['revision'], + version=dependency['state'].get('version'), + ) + + dependant_package.save() + dependant_package.print() + + print_succ(f'dd-sdk-ios dependency was successfully updated in "{args.repo_package_resolved_path}" to:') + print_succ(f' → branch: {args.dogfooded_branch}') + print_succ(f' → commit: {args.dogfooded_commit}') + +if __name__ == "__main__": + parser = argparse.ArgumentParser(description='Updates dd-sdk-ios dependency in "Package.resolved" of SDK-dependant project.') + parser.add_argument('--dogfooded-package-resolved-path', type=str, required=True, help='Path to "Package.resolved" from dd-sdk-ios') + parser.add_argument('--dogfooded-branch', type=str, required=True, help='Name of the branch to dogfood from') + parser.add_argument('--dogfooded-commit', type=str, required=True, help='SHA of the commit to dogfood') + parser.add_argument('--repo-package-resolved-path', type=str, required=True, help='Path to "Package.resolved" file in SDK-dependant project (the one to modify)') + args = parser.parse_args() + + try: + dogfood(args=args) + except Exception as error: + print_err(f'Failed to update dependency: {error}') + print('-' * 60) + traceback.print_exc(file=sys.stdout) + print('-' * 60) + sys.exit(1) + + sys.exit(0) \ No newline at end of file diff --git a/tools/license/check-license.sh b/tools/license/check-license.sh index f0b53ff6c0..6edfc36417 100755 --- a/tools/license/check-license.sh +++ b/tools/license/check-license.sh @@ -17,7 +17,7 @@ function files { -not -path "*Carthage/Build/*" \ -not -path "*Carthage/Checkouts/*" \ -not -path "./tools/rum-models-generator/rum-events-format/*" \ - -not -path "*/tools/distribution/venv/*" \ + -not -path "*/tools/dogfooding/venv/*" \ -not -path "*/tools/ci/venv/*" \ -not -path "*.xcframework/*" \ -not -path "*.xcarchive/*" \ diff --git a/tools/tools-test.sh b/tools/tools-test.sh index eb9d12183a..1a533e9509 100755 --- a/tools/tools-test.sh +++ b/tools/tools-test.sh @@ -18,8 +18,8 @@ test_swift_package tools/http-server-mock test_swift_package tools/rum-models-generator test_swift_package tools/sr-snapshots -# Test release & dogfood automation: -echo_subtitle "Run 'make clean install test' in ./tools/distribution" -cd tools/distribution && make clean install test +# Test dogfooding automation: +echo_subtitle "Run 'make clean install test' in ./tools/dogfooding" +cd tools/dogfooding && make clean install test cd - diff --git a/tools/utils/common.mk b/tools/utils/common.mk index 2fdee82c32..041c97248a 100644 --- a/tools/utils/common.mk +++ b/tools/utils/common.mk @@ -27,5 +27,6 @@ endef CURRENT_GIT_TAG := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print-tag) CURRENT_GIT_BRANCH := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print-branch) +CURRENT_GIT_COMMIT := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print-commit) CURRENT_GIT_COMMIT_SHORT := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print-commit-short) CURRENT_GIT_REF := $(shell $(REPO_ROOT)/tools/utils/current-git.sh --print) diff --git a/tools/utils/current-git.sh b/tools/utils/current-git.sh index 4a1cc7474b..66658d1387 100755 --- a/tools/utils/current-git.sh +++ b/tools/utils/current-git.sh @@ -28,17 +28,21 @@ function current_git_branch() { fi } -# Prints current commit sha (short) -function current_git_commit_short() { - if [[ -n "$CI_COMMIT_SHORT_SHA" ]]; then - echo "$CI_COMMIT_SHORT_SHA" +# Prints current git commit (full SHA) +function current_git_commit() { + if [[ -n "$CI_COMMIT_SHA" ]]; then + echo "$CI_COMMIT_SHA" else - local git_commit_short=$(git rev-parse --short HEAD) - echo "$git_commit_short" + echo "$(git rev-parse HEAD)" fi } -# Prints current tag (if any) and current branch otherwise. +# Prints the first eight characters of current commit SHA +function current_git_commit_short() { + echo $(current_git_commit | cut -c 1-8) +} + +# Prints current tag (if any) or current branch. function current_git_ref() { local tag=$(current_git_tag) if [[ -n "$tag" ]]; then @@ -58,6 +62,9 @@ case "$1" in --print-branch) current_git_branch ;; + --print-commit) + current_git_commit + ;; --print-commit-short) current_git_commit_short ;; From 7c0dd1748322ac45572958eda5edf627361b00a6 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Thu, 18 Jul 2024 13:44:09 +0200 Subject: [PATCH 30/43] update changelog and regen models --- CHANGELOG.md | 5 +++-- DatadogObjc/Sources/RUM/RUMDataModels+objc.swift | 2 +- DatadogRUM/Sources/DataModels/RUMDataModels.swift | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index aef6a1fe42..f2b257f7db 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,8 +1,8 @@ # Unreleased -- [FEATURE] Enable DatadogCore, DatadogLogs and DatadogTrace to compile on watchOS platform. See [#1918][] (Thanks [@jfiser-paylocity][]) [#1946][] +- [FEATURE] Enable DatadogCore, DatadogLogs and DatadogTrace to compile on watchOS platform. See [#1918][] (Thanks [@jfiser-paylocity][]) [#1946][] - [IMPROVEMENT] Ability to clear feature data storage using `clearAllData` API. See [#1940][] -- [IMPROVEMENT] Send memory warning as RUM error. +- [IMPROVEMENT] Send memory warning as RUM error. See [#1955][] # 2.14.1 / 09-07-2024 @@ -724,6 +724,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1947]: https://github.com/DataDog/dd-sdk-ios/pull/1947 [#1948]: https://github.com/DataDog/dd-sdk-ios/pull/1948 [#1940]: https://github.com/DataDog/dd-sdk-ios/pull/1940 +[#1955]: https://github.com/DataDog/dd-sdk-ios/pull/1955 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu diff --git a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift index e24fcee3dd..19c898ea78 100644 --- a/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift +++ b/DatadogObjc/Sources/RUM/RUMDataModels+objc.swift @@ -7710,4 +7710,4 @@ public class DDTelemetryConfigurationEventView: NSObject { // swiftlint:enable force_unwrapping -// Generated from https://github.com/DataDog/rum-events-format/tree/c853be6db33125c8767ae563d2af47b92636c4e1 +// Generated from https://github.com/DataDog/rum-events-format/tree/31c73753ff5c954cf9aef475c91ec0b413743f77 diff --git a/DatadogRUM/Sources/DataModels/RUMDataModels.swift b/DatadogRUM/Sources/DataModels/RUMDataModels.swift index ce17a786c6..59bb13fd7a 100644 --- a/DatadogRUM/Sources/DataModels/RUMDataModels.swift +++ b/DatadogRUM/Sources/DataModels/RUMDataModels.swift @@ -4335,4 +4335,4 @@ public struct RUMTelemetryOperatingSystem: Codable { } } -// Generated from https://github.com/DataDog/rum-events-format/tree/c853be6db33125c8767ae563d2af47b92636c4e1 +// Generated from https://github.com/DataDog/rum-events-format/tree/31c73753ff5c954cf9aef475c91ec0b413743f77 From 3fe3330467a383d9cab3a9d22f982ea5ec0a7c71 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 19 Jul 2024 10:00:28 +0200 Subject: [PATCH 31/43] RUM-5344 Provide GITHUB_TOKEN for Carthage on CI this is to avoid rate-limiting `carthage` commands in CI see: https://github.com/Carthage/Carthage/pull/605 --- SmokeTests/carthage/Makefile | 2 +- tools/carthage-shim.sh | 21 +++++++++++++++++++++ tools/release/build-xcframeworks.sh | 4 +++- tools/repo-setup/repo-setup.sh | 2 +- tools/secrets/config.sh | 28 +++++++++++++++------------- tools/secrets/get-secret.sh | 4 ++-- 6 files changed, 43 insertions(+), 18 deletions(-) create mode 100755 tools/carthage-shim.sh diff --git a/SmokeTests/carthage/Makefile b/SmokeTests/carthage/Makefile index e55fdb3d2a..f390306ec1 100644 --- a/SmokeTests/carthage/Makefile +++ b/SmokeTests/carthage/Makefile @@ -46,7 +46,7 @@ ifeq ($(CARTHAGE_PLATFORM),) @exit 1 endif @$(ECHO_INFO) "Using CARTHAGE_PLATFORM='$(CARTHAGE_PLATFORM)'" - carthage update --platform $(CARTHAGE_PLATFORM) --use-xcframeworks + REPO_ROOT=$(REPO_ROOT) $(REPO_ROOT)/tools/carthage-shim.sh update --platform $(CARTHAGE_PLATFORM) --use-xcframeworks test: @$(call require_param,OS) diff --git a/tools/carthage-shim.sh b/tools/carthage-shim.sh new file mode 100755 index 0000000000..de4c172fb8 --- /dev/null +++ b/tools/carthage-shim.sh @@ -0,0 +1,21 @@ +#!/bin/zsh + +# Usage: +# - in repo root: +# $ ./tools/carthage-shim.sh [carthage commands and parameters] +# - in different directory: +# $ REPO_ROOT="../../" ../../tools/carthage-shim.sh [carthage commands and parameters] +# +# Shims Carthage commands with avoiding rate-limiting on CI. + +source "${REPO_ROOT:-.}/tools/secrets/get-secret.sh" + +# "In shared environment where several virtual machines are using the same public ip address (like CI), +# carthage user could hit a Github API rate limit. By providing a Github API access token, carthage can get +# a higher rate limit." +# Ref.: https://github.com/Carthage/Carthage/pull/605 +if [ "$CI" = "true" ]; then + export GITHUB_ACCESS_TOKEN=$(get_secret $DD_IOS_SECRET__CARTHAGE_GH_TOKEN) +fi + +carthage "$@" diff --git a/tools/release/build-xcframeworks.sh b/tools/release/build-xcframeworks.sh index 94adc3f3e4..9d5c9fdfbd 100755 --- a/tools/release/build-xcframeworks.sh +++ b/tools/release/build-xcframeworks.sh @@ -25,6 +25,7 @@ parse_args "$@" REPO_PATH=$(realpath "$repo_path") +SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" echo_info "Clean '$REPO_PATH' with 'git clean -fxd'" cd "$REPO_PATH" && git clean -fxd && cd - @@ -107,7 +108,8 @@ echo_info "▸ PLATFORMS = '$PLATFORMS'" # Build third-party XCFrameworks echo_subtitle2 "Run 'carthage bootstrap --platform $PLATFORMS --use-xcframeworks'" -carthage bootstrap --platform $PLATFORMS --use-xcframeworks +export REPO_ROOT=$(realpath "$SCRIPT_DIR/../..") +$REPO_ROOT/tools/carthage-shim.sh bootstrap --platform $PLATFORMS --use-xcframeworks cp -r "Carthage/Build/CrashReporter.xcframework" "$XCFRAMEWORKS_OUTPUT" cp -r "Carthage/Build/OpenTelemetryApi.xcframework" "$XCFRAMEWORKS_OUTPUT" diff --git a/tools/repo-setup/repo-setup.sh b/tools/repo-setup/repo-setup.sh index 4e47aa3ec0..3651fb29c7 100755 --- a/tools/repo-setup/repo-setup.sh +++ b/tools/repo-setup/repo-setup.sh @@ -34,7 +34,7 @@ if [[ "$env" == "$ENV_DEV" ]]; then fi bundle install -carthage bootstrap --platform iOS,tvOS --use-xcframeworks +./tools/carthage-shim.sh bootstrap --platform iOS,tvOS --use-xcframeworks echo_succ "Using OpenTelemetryApi version: $(cat ./Carthage/Build/.OpenTelemetryApi.version | grep 'commitish' | awk -F'"' '{print $4}')" echo_succ "Using PLCrashReporter version: $(cat ./Carthage/Build/.plcrashreporter.version | grep 'commitish' | awk -F'"' '{print $4}')" diff --git a/tools/secrets/config.sh b/tools/secrets/config.sh index 8476beb4b4..aa987198ac 100644 --- a/tools/secrets/config.sh +++ b/tools/secrets/config.sh @@ -13,6 +13,7 @@ DD_IOS_SECRETS_PATH_PREFIX='kv/aws/arn:aws:iam::486234852809:role/ci-dd-sdk-ios/ # Keep this list and Confluence page up-to-date with every secret that is added to the list. DD_IOS_SECRET__TEST_SECRET="test.secret" DD_IOS_SECRET__GH_CLI_TOKEN="gh.cli.token" +DD_IOS_SECRET__CARTHAGE_GH_TOKEN="carthage.gh.token" DD_IOS_SECRET__CP_TRUNK_TOKEN="cocoapods.trunk.token" DD_IOS_SECRET__SSH_KEY="ssh.key" DD_IOS_SECRET__E2E_CERTIFICATE_P12_BASE64="e2e.certificate.p12.base64" @@ -23,16 +24,17 @@ DD_IOS_SECRET__E2E_S8S_API_KEY="e2e.s8s.api.key" DD_IOS_SECRET__E2E_S8S_APP_KEY="e2e.s8s.app.key" DD_IOS_SECRET__E2E_S8S_APPLICATION_ID="e2e.s8s.app.id" -declare -A DD_IOS_SECRETS=( - [0]="$DD_IOS_SECRET__TEST_SECRET | test secret to see if things work, free to change but not delete" - [1]="$DD_IOS_SECRET__GH_CLI_TOKEN | GitHub token to authenticate 'gh' cli (https://cli.github.com/)" - [2]="$DD_IOS_SECRET__CP_TRUNK_TOKEN | Cocoapods token to authenticate 'pod trunk' operations (https://guides.cocoapods.org/terminal/commands.html)" - [3]="$DD_IOS_SECRET__SSH_KEY | SSH key to authenticate 'git clone git@github.com:...' operations" - [4]="$DD_IOS_SECRET__E2E_CERTIFICATE_P12_BASE64 | Base64-encoded '.p12' certificate file for signing E2E app" - [5]="$DD_IOS_SECRET__E2E_CERTIFICATE_P12_PASSWORD | Password to '$DD_IOS_SECRET__E2E_CERTIFICATE_P12_BASE64' certificate" - [6]="$DD_IOS_SECRET__E2E_PROVISIONING_PROFILE_BASE64 | Base64-encoded provisioning profile file for signing E2E app" - [7]="$DD_IOS_SECRET__E2E_XCCONFIG_BASE64 | Base64-encoded xcconfig file for E2E app" - [8]="$DD_IOS_SECRET__E2E_S8S_API_KEY | DATADOG_API_KEY for uploading E2E app to synthetics" - [9]="$DD_IOS_SECRET__E2E_S8S_APP_KEY | DATADOG_APP_KEY for uploading E2E app to synthetics" - [10]="$DD_IOS_SECRET__E2E_S8S_APPLICATION_ID | Synthetics app ID for E2E tests" -) +idx=0 +declare -A DD_IOS_SECRETS +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__TEST_SECRET | test secret to see if things work, free to change but not delete" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__GH_CLI_TOKEN | GitHub token to authenticate 'gh' cli (https://cli.github.com/)" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__CARTHAGE_GH_TOKEN | GitHub token to avoid rate limiting Carthage commands (https://github.com/Carthage/Carthage/pull/605)" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__CP_TRUNK_TOKEN | Cocoapods token to authenticate 'pod trunk' operations (https://guides.cocoapods.org/terminal/commands.html)" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__SSH_KEY | SSH key to authenticate 'git clone git@github.com:...' operations" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__E2E_CERTIFICATE_P12_BASE64 | Base64-encoded '.p12' certificate file for signing E2E app" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__E2E_CERTIFICATE_P12_PASSWORD | Password to '$DD_IOS_SECRET__E2E_CERTIFICATE_P12_BASE64' certificate" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__E2E_PROVISIONING_PROFILE_BASE64 | Base64-encoded provisioning profile file for signing E2E app" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__E2E_XCCONFIG_BASE64 | Base64-encoded xcconfig file for E2E app" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__E2E_S8S_API_KEY | DATADOG_API_KEY for uploading E2E app to synthetics" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__E2E_S8S_APP_KEY | DATADOG_APP_KEY for uploading E2E app to synthetics" +DD_IOS_SECRETS[$((idx++))]="$DD_IOS_SECRET__E2E_S8S_APPLICATION_ID | Synthetics app ID for E2E tests" diff --git a/tools/secrets/get-secret.sh b/tools/secrets/get-secret.sh index 594568b5de..f336ff7a37 100755 --- a/tools/secrets/get-secret.sh +++ b/tools/secrets/get-secret.sh @@ -1,7 +1,7 @@ #!/bin/zsh -source ./tools/utils/echo-color.sh -source ./tools/secrets/config.sh +source "${REPO_ROOT:-.}/tools/utils/echo-color.sh" +source "${REPO_ROOT:-.}/tools/secrets/config.sh" # Usage: # get_secret From fb91ae72e98bff5d3a3f4170c1722cb0b4d9b41d Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 19 Jul 2024 10:00:49 +0200 Subject: [PATCH 32/43] RUM-5344 Fix Ci trigger map --- .gitlab-ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index c025e1736c..04458d834c 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -42,6 +42,7 @@ default: paths: - "Datadog*/**/*" - "IntegrationTests/**/*" + - "SmokeTests/**/*" - "TestUtilities/**/*" - "*" # match any file in the root directory compare_to: 'develop' # cannot use $DEVELOP_BRANCH var due to: https://gitlab.com/gitlab-org/gitlab/-/issues/369916 @@ -236,6 +237,9 @@ Smoke Tests (macOS): Smoke Tests (watchOS): stage: smoke-test + rules: + - !reference [.test-pipeline-job, rules] + - !reference [.release-pipeline-job, rules] tags: - macos:ventura - specific:true From 17b5be0cee679e6e2c5666a4fd988fde42e55ad1 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Fri, 19 Jul 2024 10:56:12 +0200 Subject: [PATCH 33/43] RUM-4079 CR feedback --- tools/dogfooding/dogfood.sh | 49 +++++++++++++------------ tools/dogfooding/read-dogfooded-hash.py | 44 ---------------------- 2 files changed, 26 insertions(+), 67 deletions(-) delete mode 100644 tools/dogfooding/read-dogfooded-hash.py diff --git a/tools/dogfooding/dogfood.sh b/tools/dogfooding/dogfood.sh index d13c97c746..fd5e226270 100755 --- a/tools/dogfooding/dogfood.sh +++ b/tools/dogfooding/dogfood.sh @@ -140,27 +140,35 @@ resolve_dd_sdk_ios_package() { swift package --package-path "$SDK_PACKAGE_PATH" show-dependencies } -# Reads sdk_version from current commit and stores it in DOGFOODED_SDK_VERSION variable. +# Reads sdk_version from current `dd-sdk-ios` commit and stores it in DOGFOODED_SDK_VERSION variable. read_dogfooded_version() { echo_subtitle "Read sdk_version from '$SDK_VERSION_FILE_PATH'" sdk_version=$(grep 'internal let __sdkVersion' "$SDK_VERSION_FILE_PATH" | awk -F'"' '{print $2}') - DOGFOODED_SDK_VERSION="$sdk_version-$DOGFOODED_COMMIT_SHORT" # e.g. '2.14.1-b5215c02' + # Version format is `+` utilizing build metadata from https://semver.org/ + # E.g.: 2.14.1+2f9a7df8 + DOGFOODED_SDK_VERSION="$sdk_version+$DOGFOODED_COMMIT_SHORT" echo_info "▸ SDK version is '$sdk_version'" echo_succ "▸ Using '$DOGFOODED_SDK_VERSION' for dogfooding" } -# Reads the hash of previously dogfooded commit to LAST_DOGFOODED_COMMIT variable. -read_last_dogfooded_commit() { - local package_resolved_path="$1" - echo_subtitle "Get last dogfooded commit hash in '$REPO_NAME' repo" - output=$(make run PARAMS="read-dogfooded-hash.py --repo-package-resolved-path '$package_resolved_path'") - echo "$output" - LAST_DOGFOODED_COMMIT=$(echo "$output" | awk -F 'DOGFOODED_HASH=' '/DOGFOODED_HASH/ {print $2}') +# Reads the hash of dogfooded commit from sdk version file in dependant project. +read_dogfooded_commit() { + local version_file="$1" + echo_subtitle "Read dogfooded commit from '$version_file'" >&2 + + echo_info "▸ Parsing '$version_file':" >&2 + echo_info ">>> '$version_file' begin" >&2 + cat "$version_file" >&2 + echo_info "<<< '$version_file' end" >&2 + + dogfooded_commit_sha=$(grep '__dogfoodedSDKVersion = "' "$version_file" | awk -F '[+""]' '{print $(NF-1)}') + echo "$dogfooded_commit_sha" } -# Prints changes since LAST_DOGFOODED_COMMIT. -print_changelog_since_last_dogfooded_commit() { +# Prints changelog from provided commit to current one. +print_changelog() { echo_subtitle "Generate changelog" >&2 + local from_commit="$1" if [ "$CI" = "true" ]; then # Fetch branch history and unshallow in GitLab which only does shallow clone by default @@ -169,9 +177,9 @@ print_changelog_since_last_dogfooded_commit() { fi # Read git history from last dogfooded commit to current: - echo_info "▸ Reading commits ($LAST_DOGFOODED_COMMIT..HEAD):" >&2 + echo_info "▸ Reading commits ($from_commit..HEAD):" >&2 git_log=$(git --no-pager log \ - --pretty=oneline "$LAST_DOGFOODED_COMMIT..HEAD" \ + --pretty=oneline "$from_commit..HEAD" \ --ancestry-path "origin/$DOGFOODED_BRANCH" ) echo_info ">>> git log begin" >&2 @@ -181,7 +189,7 @@ print_changelog_since_last_dogfooded_commit() { # Extract only merge commits: CHANGELOG=$(echo "$git_log" | grep -o 'Merge pull request #[0-9]\+' | awk -F'#' '{print "- https://github.com/DataDog/dd-sdk-ios/pull/"$2}' || true) if [ -z "$CHANGELOG" ]; then - CHANGELOG="- Empty (no PRs merged since https://github.com/DataDog/dd-sdk-ios/commit/$LAST_DOGFOODED_COMMIT)" + CHANGELOG="- Empty (no PRs merged since https://github.com/DataDog/dd-sdk-ios/commit/$from_commit)" fi echo_info "▸ Changelog:" >&2 @@ -208,11 +216,6 @@ update_dependant_sdk_version() { local version_file="$1" echo_subtitle "Update 'sdk_version' in '$version_file'" - echo_info ">>> '$version_file' before" - cat "$version_file" - echo_info "<<< '$version_file' before" - - # sed -i '' "s/internal let __dogfoodedSDKVersion = \".*\"/internal let __dogfoodedSDKVersion = \"$DOGFOODED_SDK_VERSION\"/" "$version_file" sed -i '' -E "s/(let __dogfoodedSDKVersion = \")[^\"]*(\")/\1${DOGFOODED_SDK_VERSION}\2/" "$version_file" echo_succ "▸ Updated '$version_file' to:" @@ -235,8 +238,8 @@ if [ "$shopist" = "true" ]; then clone_repo "git@github.com:DataDog/shopist-ios.git" $DEFAULT_BRANCH $CLONE_PATH # Generate CHANGELOG: - read_last_dogfooded_commit "$CLONE_PATH/Shopist/Shopist.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" - CHANGELOG=$(print_changelog_since_last_dogfooded_commit) + LAST_DOGFOODED_COMMIT=$(read_dogfooded_commit "$CLONE_PATH/Shopist/Shopist/DogfoodingConfig.swift") + CHANGELOG=$(print_changelog "$LAST_DOGFOODED_COMMIT") # Update dd-sdk-ios version: update_dependant_package_resolved "$CLONE_PATH/Shopist/Shopist.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved" @@ -256,8 +259,8 @@ if [ "$datadog_app" = "true" ]; then clone_repo "git@github.com:DataDog/datadog-ios.git" $DEFAULT_BRANCH $CLONE_PATH # Generate CHANGELOG: - read_last_dogfooded_commit "$CLONE_PATH/DatadogApp.xcworkspace/xcshareddata/swiftpm/Package.resolved" - CHANGELOG=$(print_changelog_since_last_dogfooded_commit) + LAST_DOGFOODED_COMMIT=$(read_dogfooded_commit "$CLONE_PATH/Targets/DogLogger/Datadog/DogfoodingConfig.swift") + CHANGELOG=$(print_changelog "$LAST_DOGFOODED_COMMIT") # Update dd-sdk-ios version: update_dependant_package_resolved "$CLONE_PATH/DatadogApp.xcworkspace/xcshareddata/swiftpm/Package.resolved" diff --git a/tools/dogfooding/read-dogfooded-hash.py b/tools/dogfooding/read-dogfooded-hash.py deleted file mode 100644 index 3ade98d56e..0000000000 --- a/tools/dogfooding/read-dogfooded-hash.py +++ /dev/null @@ -1,44 +0,0 @@ -#!/usr/bin/env python3 -# -*- coding: utf-8 -*- - -# ----------------------------------------------------------- -# Unless explicitly stated otherwise all files in this repository are licensed under the Apache License Version 2.0. -# This product includes software developed at Datadog (https://www.datadoghq.com/). -# Copyright 2019-Present Datadog, Inc. -# ----------------------------------------------------------- - -import sys -import traceback -import argparse -from src.dogfood.package_resolved import PackageResolvedFile, PackageID -from src.utils import print_err, print_info - -def print_to_stdout(hash): - """ - Prints the hash in a format that is recognized by caller script. - """ - print(f'DOGFOODED_HASH={hash}') - -if __name__ == "__main__": - parser = argparse.ArgumentParser(description='Reads the hash of dd-sdk-ios dependency in "Package.resolved" of SDK-dependant project and prints it to STDOUT.') - parser.add_argument('--repo-package-resolved-path', type=str, required=True, help='Path to "Package.resolved" file in SDK-dependant project') - args = parser.parse_args() - - try: - dependant_package = PackageResolvedFile(path=args.repo_package_resolved_path) - dependency = dependant_package.read_dependency(package_id=PackageID(v1='DatadogSDK', v2='dd-sdk-ios')) - print_info(f"▸ Found dd-sdk-ios dependency in '{args.repo_package_resolved_path}': {dependency}") - hash=dependency['state'].get('revision') - - if not hash: - raise Exception(f'Dogfooded dependency is missing hash: {dependency}') - - print_to_stdout(hash=hash) - except Exception as error: - print_err(f'Failed to get last dependency hash: {error}') - print('-' * 60) - traceback.print_exc(file=sys.stdout) - print('-' * 60) - sys.exit(1) - - sys.exit(0) \ No newline at end of file From 783407bc8ea9d5027e874b59212116993a8bea2a Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 19 Jul 2024 11:40:05 +0200 Subject: [PATCH 34/43] RUM-4709 feat: decorate network span kind as `client` --- CHANGELOG.md | 2 ++ .../Datadog/Tracing/TracingURLSessionHandlerTests.swift | 6 ++++-- .../Sources/Integrations/TracingURLSessionHandler.swift | 2 ++ DatadogTrace/Sources/TraceConfiguration.swift | 1 + DatadogTrace/Sources/Tracer.swift | 3 +++ 5 files changed, 12 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f2b257f7db..660d5e48ae 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ - [FEATURE] Enable DatadogCore, DatadogLogs and DatadogTrace to compile on watchOS platform. See [#1918][] (Thanks [@jfiser-paylocity][]) [#1946][] - [IMPROVEMENT] Ability to clear feature data storage using `clearAllData` API. See [#1940][] - [IMPROVEMENT] Send memory warning as RUM error. See [#1955][] +- [IMPROVEMENT] Decorate network span kind as `client`. See [#1963][] # 2.14.1 / 09-07-2024 @@ -725,6 +726,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1948]: https://github.com/DataDog/dd-sdk-ios/pull/1948 [#1940]: https://github.com/DataDog/dd-sdk-ios/pull/1940 [#1955]: https://github.com/DataDog/dd-sdk-ios/pull/1955 +[#1963]: https://github.com/DataDog/dd-sdk-ios/pull/1963 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu diff --git a/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift b/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift index 8b88bf6b4f..66c2860978 100644 --- a/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift +++ b/DatadogCore/Tests/Datadog/Tracing/TracingURLSessionHandlerTests.swift @@ -109,12 +109,13 @@ class TracingURLSessionHandlerTests: XCTestCase { XCTAssertEqual(span.tags[OTTags.httpUrl], request.url!.absoluteString) XCTAssertEqual(span.tags[OTTags.httpMethod], "GET") XCTAssertEqual(span.tags[SpanTags.errorType], "domain - 123") + XCTAssertEqual(span.tags[SpanTags.kind], "client") XCTAssertEqual( span.tags[SpanTags.errorStack], "Error Domain=domain Code=123 \"network error\" UserInfo={NSLocalizedDescription=network error}" ) XCTAssertEqual(span.tags[SpanTags.errorMessage], "network error") - XCTAssertEqual(span.tags.count, 7) + XCTAssertEqual(span.tags.count, 8) let log: LogEvent = try XCTUnwrap(core.events().last, "It should send error log") XCTAssertEqual(log.status, .error) @@ -178,11 +179,12 @@ class TracingURLSessionHandlerTests: XCTestCase { XCTAssertEqual(span.tags[OTTags.httpStatusCode], "404") XCTAssertEqual(span.tags[SpanTags.errorType], "HTTPURLResponse - 404") XCTAssertEqual(span.tags[SpanTags.errorMessage], "404 not found") + XCTAssertEqual(span.tags[SpanTags.kind], "client") XCTAssertEqual( span.tags[SpanTags.errorStack], "Error Domain=HTTPURLResponse Code=404 \"404 not found\" UserInfo={NSLocalizedDescription=404 not found}" ) - XCTAssertEqual(span.tags.count, 8) + XCTAssertEqual(span.tags.count, 9) let log: LogEvent = try XCTUnwrap(core.events().last, "It should send error log") XCTAssertEqual(log.status, .error) diff --git a/DatadogTrace/Sources/Integrations/TracingURLSessionHandler.swift b/DatadogTrace/Sources/Integrations/TracingURLSessionHandler.swift index 8a30ed8e0a..285e30ecef 100644 --- a/DatadogTrace/Sources/Integrations/TracingURLSessionHandler.swift +++ b/DatadogTrace/Sources/Integrations/TracingURLSessionHandler.swift @@ -137,6 +137,8 @@ internal struct TracingURLSessionHandler: DatadogURLSessionHandler { return } + span.setTag(key: SpanTags.kind, value: "client") + let url = interception.request.url?.absoluteString ?? "unknown_url" if let requestUrl = interception.request.url { diff --git a/DatadogTrace/Sources/TraceConfiguration.swift b/DatadogTrace/Sources/TraceConfiguration.swift index abb12e66e2..205239360a 100644 --- a/DatadogTrace/Sources/TraceConfiguration.swift +++ b/DatadogTrace/Sources/TraceConfiguration.swift @@ -19,6 +19,7 @@ import DatadogInternal @_exported import class DatadogInternal.W3CHTTPHeadersWriter @_exported import enum DatadogInternal.TraceSamplingStrategy @_exported import enum DatadogInternal.TraceContextInjection +@_exported import enum DatadogInternal.TracingHeaderType // swiftlint:enable duplicate_imports extension Trace { diff --git a/DatadogTrace/Sources/Tracer.swift b/DatadogTrace/Sources/Tracer.swift index 05147d18f0..50d26355f8 100644 --- a/DatadogTrace/Sources/Tracer.swift +++ b/DatadogTrace/Sources/Tracer.swift @@ -42,6 +42,9 @@ public enum SpanTags { internal static let rumViewID = "_dd.view.id" /// Internal tag used to encode the RUM action ID, linking the span to the current RUM session. internal static let rumActionID = "_dd.action.id" + /// Internal tag used to encode the span kind. This can be either "client" or "server" for RPC spans, + /// and "producer" or "consumer" for messaging spans. + internal static let kind = "span.kind" } /// A class for manual interaction with the Trace feature. It records spans that are sent to Datadog APM. From c8529ba976f62fc3abe0e1d8e4eda505fca43e09 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Fri, 19 Jul 2024 12:02:06 +0200 Subject: [PATCH 35/43] fix test case --- DatadogTrace/Tests/TracingURLSessionHandlerTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift b/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift index 57353102fa..6d6bcf4892 100644 --- a/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift +++ b/DatadogTrace/Tests/TracingURLSessionHandlerTests.swift @@ -289,7 +289,8 @@ class TracingURLSessionHandlerTests: XCTestCase { XCTAssertEqual(span.tags[OTTags.httpUrl], request.url!.absoluteString) XCTAssertEqual(span.tags[OTTags.httpMethod], "POST") XCTAssertEqual(span.tags[OTTags.httpStatusCode], "200") - XCTAssertEqual(span.tags.count, 5) + XCTAssertEqual(span.tags[OTTags.spanKind], "client") + XCTAssertEqual(span.tags.count, 6) } func testGivenFirstPartyIncompleteInterception_whenInterceptionCompletes_itDoesNotSendTheSpan() throws { From bcee4ee42ee2b8f91c22979f58c42e9de9a3a79c Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Mon, 22 Jul 2024 09:32:02 +0200 Subject: [PATCH 36/43] RUM-4079 Do not block pipeline with manual dogfooding jobs --- .gitlab-ci.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 82a7265346..9f1a1593e5 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -278,6 +278,7 @@ Dogfood (Shopist): rules: - if: '$CI_COMMIT_BRANCH == $DEVELOP_BRANCH' when: manual + allow_failure: true script: - ./tools/runner-setup.sh --ssh # temporary, waiting for AMI - DRY_RUN=0 make dogfood-shopist @@ -287,6 +288,7 @@ Dogfood (Datadog app): rules: - if: '$CI_COMMIT_BRANCH == $DEVELOP_BRANCH' when: manual + allow_failure: true script: - ./tools/runner-setup.sh --ssh # temporary, waiting for AMI - DRY_RUN=0 make dogfood-datadog-app From 5fcfdd117b1720555ebac601962eb89d5bdc2667 Mon Sep 17 00:00:00 2001 From: Ganesh Jangir Date: Wed, 24 Jul 2024 16:59:48 +0200 Subject: [PATCH 37/43] fix: do not send context updates on context read --- .../Core/Context/DatadogContextProvider.swift | 11 +++++++---- .../Context/DatadogContextProviderTests.swift | 19 ------------------- .../WatchdogTerminationMonitor.swift | 12 ++++++++---- .../WatchdogTerminationMonitorTests.swift | 4 ++-- 4 files changed, 17 insertions(+), 29 deletions(-) diff --git a/DatadogCore/Sources/Core/Context/DatadogContextProvider.swift b/DatadogCore/Sources/Core/Context/DatadogContextProvider.swift index b328816b44..f779bfb3ee 100644 --- a/DatadogCore/Sources/Core/Context/DatadogContextProvider.swift +++ b/DatadogCore/Sources/Core/Context/DatadogContextProvider.swift @@ -40,9 +40,7 @@ internal final class DatadogContextProvider { /// The current `context`. /// /// The value must be accessed from the `queue` only. - private var context: DatadogContext { - didSet { receivers.forEach { $0(context) } } - } + private var context: DatadogContext /// The queue used to synchronize the access to the `DatadogContext`. internal let queue = DispatchQueue( @@ -110,7 +108,12 @@ internal final class DatadogContextProvider { /// /// - Parameter block: The block closure called with the current context. func write(block: @escaping (inout DatadogContext) -> Void) { - queue.async { block(&self.context) } + queue.async { + block(&self.context) + self.receivers.forEach { receiver in + receiver(self.context) + } + } } /// Subscribes a context's property to a publisher. diff --git a/DatadogCore/Tests/Datadog/DatadogCore/Context/DatadogContextProviderTests.swift b/DatadogCore/Tests/Datadog/DatadogCore/Context/DatadogContextProviderTests.swift index 2dedf378d9..66818267d2 100644 --- a/DatadogCore/Tests/Datadog/DatadogCore/Context/DatadogContextProviderTests.swift +++ b/DatadogCore/Tests/Datadog/DatadogCore/Context/DatadogContextProviderTests.swift @@ -91,25 +91,6 @@ class DatadogContextProviderTests: XCTestCase { wait(for: [expectation], timeout: 0.5) } - func testPublishContextOnContextRead() throws { - let expectation = self.expectation(description: "publish new context") - expectation.expectedFulfillmentCount = 3 - - // Given - - let provider = DatadogContextProvider(context: context) - provider.publish { _ in - expectation.fulfill() - } - - // When - (0.. Bool { - feature.context { [weak self] context in + guard case .context(let context) = message else { + return false + } + + if currentState == .stopped { do { guard let launchReport = try context.baggages[LaunchReport.baggageKey]?.decode(type: LaunchReport.self) else { - return + return false } - self?.start(launchReport: launchReport) + self.start(launchReport: launchReport) } catch { DD.logger.error(ErrorMessages.failedToDecodeLaunchReport, error: error) - self?.feature.telemetry.error(ErrorMessages.failedToDecodeLaunchReport, error: error) + self.feature.telemetry.error(ErrorMessages.failedToDecodeLaunchReport, error: error) } } diff --git a/DatadogRUM/Tests/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitorTests.swift b/DatadogRUM/Tests/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitorTests.swift index c55a0689ff..ab3ac7fccf 100644 --- a/DatadogRUM/Tests/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitorTests.swift +++ b/DatadogRUM/Tests/Instrumentation/WatchdogTerminations/WatchdogTerminationMonitorTests.swift @@ -38,7 +38,7 @@ final class WatchdogTerminationMonitorTests: XCTestCase { sut.update(viewEvent: viewEvent1) // monitor reveives the launch report - _ = sut.receive(message: .context(.mockAny()), from: NOPDatadogCore()) + _ = sut.receive(message: .context(featureScope.contextMock), from: NOPDatadogCore()) // RUM view update after start let viewEvent2: RUMViewEvent = .mockRandom() @@ -62,7 +62,7 @@ final class WatchdogTerminationMonitorTests: XCTestCase { sut.update(viewEvent: viewEvent3) // monitor reveives the launch report - _ = sut.receive(message: .context(.mockAny()), from: NOPDatadogCore()) + _ = sut.receive(message: .context(featureScope.contextMock), from: NOPDatadogCore()) waitForExpectations(timeout: 1) XCTAssertEqual(reporter.sendParams?.viewEvent.view.id, viewEvent2.view.id) From c19beab7cb5334bd3aa507418233d50fd88e9e80 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Wed, 24 Jul 2024 14:04:23 +0100 Subject: [PATCH 38/43] Make recorder identifier explicit --- .../NodeRecorders/NodeRecorder.swift | 5 +- .../UIActivityIndicatorRecorder.swift | 7 +- .../NodeRecorders/UIDatePickerRecorder.swift | 73 ++++++++++++------- .../NodeRecorders/UIImageViewRecorder.swift | 4 +- .../NodeRecorders/UILabelRecorder.swift | 4 +- .../UINavigationBarRecorder.swift | 6 +- .../NodeRecorders/UIPickerViewRecorder.swift | 9 ++- .../UIProgressViewRecorder.swift | 6 +- .../NodeRecorders/UISegmentRecorder.swift | 6 +- .../NodeRecorders/UISliderRecorder.swift | 6 +- .../NodeRecorders/UIStepperRecorder.swift | 6 +- .../NodeRecorders/UISwitchRecorder.swift | 6 +- .../NodeRecorders/UITabBarRecorder.swift | 11 ++- .../NodeRecorders/UITextFieldRecorder.swift | 10 ++- .../NodeRecorders/UITextViewRecorder.swift | 6 +- .../NodeRecorders/UIViewRecorder.swift | 4 +- .../UnsupportedViewRecorder.swift | 7 +- .../NodeRecorders/WKWebViewRecorder.swift | 6 +- .../ViewTreeSnapshotBuilder.swift | 34 ++++----- 19 files changed, 149 insertions(+), 67 deletions(-) diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/NodeRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/NodeRecorder.swift index cf9a3c00dd..9ed5f289d4 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/NodeRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/NodeRecorder.swift @@ -21,7 +21,10 @@ public protocol SessionReplayNodeRecorder { /// - Returns: the value of `NodeSemantics` or `nil` if the view is a member of view subclass other than the one this recorder is specialised for. func semantics(of view: UIView, with attributes: SessionReplayViewAttributes, in context: SessionReplayViewTreeRecordingContext) -> SessionReplayNodeSemantics? - /// Unique identifier of the node recorder. + /// Unique identifier for the node recorder, used to generate unique wireframe IDs. + /// + /// Note: Node recorders of the same type but with different UUIDs will generate different IDs for the same views. + /// To maintain consistency, propagate the parent UUID when nesting node recorders. var identifier: UUID { get } } diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIActivityIndicatorRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIActivityIndicatorRecorder.swift index e9c592144f..514a39794b 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIActivityIndicatorRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIActivityIndicatorRecorder.swift @@ -8,7 +8,11 @@ import UIKit internal struct UIActivityIndicatorRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { guard let activityIndicator = view as? UIActivityIndicatorView else { @@ -38,6 +42,7 @@ internal struct UIActivityIndicatorRecorder: NodeRecorder { let subtreeViewRecorder = ViewTreeRecorder( nodeRecorders: [ UIImageViewRecorder( + identifier: identifier, shouldRecordImagePredicate: { $0.image != nil } ) ] diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIDatePickerRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIDatePickerRecorder.swift index 7e4c63f4d1..4ddd7f1c36 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIDatePickerRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIDatePickerRecorder.swift @@ -8,10 +8,18 @@ import UIKit internal struct UIDatePickerRecorder: NodeRecorder { - let identifier = UUID() - private let wheelsStyleRecorder = WheelsStyleDatePickerRecorder() - private let compactStyleRecorder = CompactStyleDatePickerRecorder() - private let inlineStyleRecorder = InlineStyleDatePickerRecorder() + internal let identifier: UUID + + private let wheelsStyleRecorder: WheelsStyleDatePickerRecorder + private let compactStyleRecorder: CompactStyleDatePickerRecorder + private let inlineStyleRecorder: InlineStyleDatePickerRecorder + + init(identifier: UUID) { + self.identifier = identifier + self.wheelsStyleRecorder = WheelsStyleDatePickerRecorder(identifier: identifier) + self.compactStyleRecorder = CompactStyleDatePickerRecorder(identifier: identifier) + self.inlineStyleRecorder = InlineStyleDatePickerRecorder(identifier: identifier) + } func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { guard let datePicker = view as? UIDatePicker else { @@ -70,15 +78,20 @@ internal struct UIDatePickerRecorder: NodeRecorder { } private struct WheelsStyleDatePickerRecorder { - let pickerTreeRecorder = ViewTreeRecorder( - nodeRecorders: [ - UIPickerViewRecorder( - textObfuscator: { context in - return context.recorder.privacy.staticTextObfuscator - } - ) - ] - ) + private let pickerTreeRecorder: ViewTreeRecorder + + init(identifier: UUID) { + self.pickerTreeRecorder = ViewTreeRecorder( + nodeRecorders: [ + UIPickerViewRecorder( + identifier: identifier, + textObfuscator: { context in + return context.recorder.privacy.staticTextObfuscator + } + ) + ] + ) + } func record(_ view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> [Node] { return pickerTreeRecorder.record(view, in: context) @@ -90,9 +103,10 @@ private struct InlineStyleDatePickerRecorder { let labelRecorder: UILabelRecorder let subtreeRecorder: ViewTreeRecorder - init() { - self.viewRecorder = UIViewRecorder() + init(identifier: UUID) { + self.viewRecorder = UIViewRecorder(identifier: identifier) self.labelRecorder = UILabelRecorder( + identifier: identifier, textObfuscator: { context in return context.recorder.privacy.staticTextObfuscator } @@ -101,8 +115,8 @@ private struct InlineStyleDatePickerRecorder { nodeRecorders: [ viewRecorder, labelRecorder, - UIImageViewRecorder(), - UISegmentRecorder(), // iOS 14.x uses `UISegmentedControl` for "AM | PM" + UIImageViewRecorder(identifier: identifier), + UISegmentRecorder(identifier: identifier), // iOS 14.x uses `UISegmentedControl` for "AM | PM" ] ) } @@ -132,16 +146,21 @@ private struct InlineStyleDatePickerRecorder { } private struct CompactStyleDatePickerRecorder { - let subtreeRecorder = ViewTreeRecorder( - nodeRecorders: [ - UIViewRecorder(), - UILabelRecorder( - textObfuscator: { context in - return context.recorder.privacy.staticTextObfuscator - } - ) - ] - ) + let subtreeRecorder: ViewTreeRecorder + + init(identifier: UUID) { + self.subtreeRecorder = ViewTreeRecorder( + nodeRecorders: [ + UIViewRecorder(identifier: identifier), + UILabelRecorder( + identifier: identifier, + textObfuscator: { context in + return context.recorder.privacy.staticTextObfuscator + } + ) + ] + ) + } func record(_ view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> [Node] { return subtreeRecorder.record(view, in: context) diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorder.swift index e5fa578e40..53d43fa0c0 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorder.swift @@ -8,7 +8,7 @@ import UIKit internal struct UIImageViewRecorder: NodeRecorder { - internal let identifier = UUID() + internal let identifier: UUID private let tintColorProvider: (UIImageView) -> UIColor? private let shouldRecordImagePredicate: (UIImageView) -> Bool @@ -18,6 +18,7 @@ internal struct UIImageViewRecorder: NodeRecorder { } internal init( + identifier: UUID, tintColorProvider: @escaping (UIImageView) -> UIColor? = { imageView in if #available(iOS 13.0, *), let image = imageView.image { return image.isTinted ? imageView.tintColor : nil @@ -33,6 +34,7 @@ internal struct UIImageViewRecorder: NodeRecorder { } } ) { + self.identifier = identifier self.tintColorProvider = tintColorProvider self.shouldRecordImagePredicate = shouldRecordImagePredicate } diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UILabelRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UILabelRecorder.swift index d57a351df8..bd4b4bcece 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UILabelRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UILabelRecorder.swift @@ -8,18 +8,20 @@ import UIKit internal class UILabelRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID /// An option for customizing wireframes builder created by this recorder. var builderOverride: (UILabelWireframesBuilder) -> UILabelWireframesBuilder var textObfuscator: (ViewTreeRecordingContext) -> TextObfuscating init( + identifier: UUID, builderOverride: @escaping (UILabelWireframesBuilder) -> UILabelWireframesBuilder = { $0 }, textObfuscator: @escaping (ViewTreeRecordingContext) -> TextObfuscating = { context in return context.recorder.privacy.staticTextObfuscator } ) { + self.identifier = identifier self.builderOverride = builderOverride self.textObfuscator = textObfuscator } diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorder.swift index 1532c37fd3..f41aadd9b2 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorder.swift @@ -8,7 +8,11 @@ import UIKit internal struct UINavigationBarRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { guard let navigationBar = view as? UINavigationBar else { diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIPickerViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIPickerViewRecorder.swift index e3594158b6..c7f4d3f9d1 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIPickerViewRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIPickerViewRecorder.swift @@ -17,7 +17,8 @@ import UIKit /// - Instead, we infer the value by traversing picker's subtree and finding texts that have no "3D wheel" effect applied. /// - If privacy mode is elevated, we don't replace individual characters with "x" letter - instead we change whole options to fixed-width mask value. internal struct UIPickerViewRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + /// Records all shapes in picker's subtree. /// It is used to capture the background of selected option. private let selectionRecorder: ViewTreeRecorder @@ -26,14 +27,17 @@ internal struct UIPickerViewRecorder: NodeRecorder { private let labelsRecorder: ViewTreeRecorder init( + identifier: UUID, textObfuscator: @escaping (ViewTreeRecordingContext) -> TextObfuscating = { context in return context.recorder.privacy.inputAndOptionTextObfuscator } ) { - self.selectionRecorder = ViewTreeRecorder(nodeRecorders: [UIViewRecorder()]) + self.identifier = identifier + self.selectionRecorder = ViewTreeRecorder(nodeRecorders: [UIViewRecorder(identifier: identifier)]) self.labelsRecorder = ViewTreeRecorder( nodeRecorders: [ UIViewRecorder( + identifier: identifier, semanticsOverride: { view, attributes in if #available(iOS 13.0, *) { if !attributes.isVisible || attributes.alpha < 1 || !CATransform3DIsIdentity(view.transform3D) { @@ -46,6 +50,7 @@ internal struct UIPickerViewRecorder: NodeRecorder { } ), UILabelRecorder( + identifier: identifier, builderOverride: { builder in var builder = builder builder.textAlignment = .center diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorder.swift index 86646de2be..69d28a2e64 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorder.swift @@ -8,7 +8,11 @@ import UIKit internal struct UIProgressViewRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { guard let progressView = view as? UIProgressView else { diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISegmentRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISegmentRecorder.swift index 03ad65ac80..d49120a149 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISegmentRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISegmentRecorder.swift @@ -8,7 +8,11 @@ import UIKit internal struct UISegmentRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } var textObfuscator: (ViewTreeRecordingContext) -> TextObfuscating = { context in return context.recorder.privacy.inputAndOptionTextObfuscator } diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISliderRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISliderRecorder.swift index 443a2b564f..d9c33e1da8 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISliderRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISliderRecorder.swift @@ -8,7 +8,11 @@ import UIKit internal struct UISliderRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { guard let slider = view as? UISlider else { diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIStepperRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIStepperRecorder.swift index 60e04130d9..9756a978de 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIStepperRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIStepperRecorder.swift @@ -8,7 +8,11 @@ import UIKit internal struct UIStepperRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { guard let stepper = view as? UIStepper else { diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISwitchRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISwitchRecorder.swift index 8280ba6411..1741328eb7 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISwitchRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISwitchRecorder.swift @@ -8,7 +8,11 @@ import UIKit internal struct UISwitchRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { guard let `switch` = view as? UISwitch else { diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift index 2a7395251c..47c3e5cf2d 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorder.swift @@ -8,7 +8,11 @@ import UIKit internal final class UITabBarRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { guard let tabBar = view as? UITabBar else { @@ -32,6 +36,7 @@ internal final class UITabBarRecorder: NodeRecorder { let subtreeViewRecorder = ViewTreeRecorder( nodeRecorders: [ UIImageViewRecorder( + identifier: identifier, tintColorProvider: { imageView in guard let imageViewImage = imageView.image else { return nil @@ -58,9 +63,9 @@ internal final class UITabBarRecorder: NodeRecorder { return tabBar.tintColor ?? SystemColors.systemBlue } ), - UILabelRecorder(), + UILabelRecorder(identifier: identifier), // This is for recording the badge view - UIViewRecorder() + UIViewRecorder(identifier: identifier) ] ) diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextFieldRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextFieldRecorder.swift index a284d0cc5d..fdeb58dc29 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextFieldRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextFieldRecorder.swift @@ -8,7 +8,8 @@ import UIKit internal struct UITextFieldRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + /// `UIViewRecorder` for recording appearance of the text field. private let backgroundViewRecorder: UIViewRecorder /// `UIImageViewRecorder` for recording icons that are displayed in text field. @@ -25,9 +26,10 @@ internal struct UITextFieldRecorder: NodeRecorder { } } - init() { - self.backgroundViewRecorder = UIViewRecorder() - self.iconsRecorder = UIImageViewRecorder() + init(identifier: UUID) { + self.identifier = identifier + self.backgroundViewRecorder = UIViewRecorder(identifier: identifier) + self.iconsRecorder = UIImageViewRecorder(identifier: identifier) self.subtreeRecorder = ViewTreeRecorder(nodeRecorders: [backgroundViewRecorder, iconsRecorder]) } diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextViewRecorder.swift index eb0ca89675..e6fac51826 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextViewRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextViewRecorder.swift @@ -8,7 +8,11 @@ import UIKit internal struct UITextViewRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } var textObfuscator: (ViewTreeRecordingContext, _ isSensitive: Bool, _ isEditable: Bool) -> TextObfuscating = { context, isSensitive, isEditable in if isSensitive { diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIViewRecorder.swift index 50805acbeb..f8aa3eec25 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIViewRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIViewRecorder.swift @@ -8,14 +8,16 @@ import UIKit internal class UIViewRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID /// An option for overriding default semantics from parent recorder. var semanticsOverride: (UIView, ViewAttributes) -> NodeSemantics? init( + identifier: UUID, semanticsOverride: @escaping (UIView, ViewAttributes) -> NodeSemantics? = { _, _ in nil } ) { + self.identifier = identifier self.semanticsOverride = semanticsOverride } diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorder.swift index 39beee50ca..d54a16b5d2 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorder.swift @@ -10,7 +10,12 @@ import WebKit import SwiftUI internal struct UnsupportedViewRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } + // swiftlint:disable opening_brace private let unsupportedViewsPredicates: [(UIView, ViewTreeRecordingContext) -> Bool] = [ { _, context in context.viewControllerContext.isRootView(of: .safari) }, diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorder.swift index ee183805fa..85b8ea020e 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorder.swift @@ -9,7 +9,11 @@ import UIKit import WebKit internal class WKWebViewRecorder: NodeRecorder { - let identifier = UUID() + internal let identifier: UUID + + init(identifier: UUID) { + self.identifier = identifier + } func semantics(of view: UIView, with attributes: ViewAttributes, in context: ViewTreeRecordingContext) -> NodeSemantics? { guard let webView = view as? WKWebView else { diff --git a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift index 5f3b2fff44..7d195a5db6 100644 --- a/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift +++ b/DatadogSessionReplay/Sources/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/ViewTreeSnapshotBuilder.swift @@ -58,23 +58,23 @@ extension ViewTreeSnapshotBuilder { /// An arrays of default node recorders executed for the root view-tree hierarchy. internal func createDefaultNodeRecorders() -> [NodeRecorder] { return [ - UnsupportedViewRecorder(), - UIViewRecorder(), - UILabelRecorder(), - UIImageViewRecorder(), - UITextFieldRecorder(), - UITextViewRecorder(), - UISwitchRecorder(), - UISliderRecorder(), - UISegmentRecorder(), - UIStepperRecorder(), - UINavigationBarRecorder(), - UITabBarRecorder(), - UIPickerViewRecorder(), - UIDatePickerRecorder(), - WKWebViewRecorder(), - UIProgressViewRecorder(), - UIActivityIndicatorRecorder() + UnsupportedViewRecorder(identifier: UUID()), + UIViewRecorder(identifier: UUID()), + UILabelRecorder(identifier: UUID()), + UIImageViewRecorder(identifier: UUID()), + UITextFieldRecorder(identifier: UUID()), + UITextViewRecorder(identifier: UUID()), + UISwitchRecorder(identifier: UUID()), + UISliderRecorder(identifier: UUID()), + UISegmentRecorder(identifier: UUID()), + UIStepperRecorder(identifier: UUID()), + UINavigationBarRecorder(identifier: UUID()), + UITabBarRecorder(identifier: UUID()), + UIPickerViewRecorder(identifier: UUID()), + UIDatePickerRecorder(identifier: UUID()), + WKWebViewRecorder(identifier: UUID()), + UIProgressViewRecorder(identifier: UUID()), + UIActivityIndicatorRecorder(identifier: UUID()) ] } #endif From 09a4a8d0bb2f0c2cdea85569aec73b459a0a321d Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Wed, 24 Jul 2024 14:19:05 +0100 Subject: [PATCH 39/43] Add test --- .../UIActivityIndicatorRecorderTests.swift | 2 +- .../UIDatePickerRecorderTests.swift | 2 +- .../UIImageViewRecorderTests.swift | 6 ++--- .../NodeRecorders/UILabelRecorderTests.swift | 2 +- .../UINavigationBarRecorderTests.swift | 2 +- .../UIPickerViewRecorderTests.swift | 2 +- .../UIProgressViewRecorderTests.swift | 2 +- .../UISegmentRecorderTests.swift | 2 +- .../NodeRecorders/UISliderRecorderTests.swift | 2 +- .../UIStepperRecorderTests.swift | 2 +- .../NodeRecorders/UISwitchRecorderTests.swift | 2 +- .../NodeRecorders/UITabBarRecorderTests.swift | 22 ++++++++++++++++++- .../UITextFieldRecorderTests.swift | 2 +- .../UITextViewRecorderTests.swift | 2 +- .../NodeRecorders/UIViewRecorderTests.swift | 2 +- .../UnsupportedViewRecorderTests.swift | 2 +- .../WKWebViewRecorderTests.swift | 2 +- 17 files changed, 39 insertions(+), 19 deletions(-) diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIActivityIndicatorRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIActivityIndicatorRecorderTests.swift index e0e3e871bc..5ed2e0e728 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIActivityIndicatorRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIActivityIndicatorRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UIActivityIndicatorRecorderTests: XCTestCase { - private let recorder = UIActivityIndicatorRecorder() + private let recorder = UIActivityIndicatorRecorder(identifier: UUID()) private let activityIndicator = UIActivityIndicatorView() private var viewAttributes: ViewAttributes = .mockAny() diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIDatePickerRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIDatePickerRecorderTests.swift index 61eae4a363..55ee51ca4a 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIDatePickerRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIDatePickerRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UIDatePickerRecorderTests: XCTestCase { - private let recorder = UIDatePickerRecorder() + private let recorder = UIDatePickerRecorder(identifier: UUID()) private let datePicker = UIDatePicker() private var viewAttributes: ViewAttributes = .mockAny() diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorderTests.swift index 778bdd40d5..993c5e919a 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIImageViewRecorderTests.swift @@ -12,7 +12,7 @@ import TestUtilities // swiftlint:disable opening_brace class UIImageViewRecorderTests: XCTestCase { - private let recorder = UIImageViewRecorder() + private let recorder = UIImageViewRecorder(identifier: UUID()) /// The view under test. private let imageView = UIImageView() /// `ViewAttributes` simulating common attributes of image view's `UIView`. @@ -55,7 +55,7 @@ class UIImageViewRecorderTests: XCTestCase { func testWhenShouldRecordImagePredicateReturnsFalse() throws { // When - let recorder = UIImageViewRecorder(shouldRecordImagePredicate: { _ in return false }) + let recorder = UIImageViewRecorder(identifier: UUID(), shouldRecordImagePredicate: { _ in return false }) imageView.image = UIImage() viewAttributes = .mock(fixture: .visible()) @@ -69,7 +69,7 @@ class UIImageViewRecorderTests: XCTestCase { func testWhenShouldRecordImagePredicateReturnsTrue() throws { // When - let recorder = UIImageViewRecorder(shouldRecordImagePredicate: { _ in return true }) + let recorder = UIImageViewRecorder(identifier: UUID(), shouldRecordImagePredicate: { _ in return true }) imageView.image = UIImage() viewAttributes = .mock(fixture: .visible()) diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UILabelRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UILabelRecorderTests.swift index ca24da4bbf..886788148d 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UILabelRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UILabelRecorderTests.swift @@ -12,7 +12,7 @@ import TestUtilities // swiftlint:disable opening_brace class UILabelRecorderTests: XCTestCase { - private let recorder = UILabelRecorder() + private let recorder = UILabelRecorder(identifier: UUID()) /// The label under test. private let label = UILabel() /// `ViewAttributes` simulating common attributes of label's `UIView`. diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift index 857ddb5d5e..06434cdd53 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UINavigationBarRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UINavigationBarRecorderTests: XCTestCase { - private let recorder = UINavigationBarRecorder() + private let recorder = UINavigationBarRecorder(identifier: UUID()) func testWhenViewIsOfExpectedType() throws { // Given diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIPickerViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIPickerViewRecorderTests.swift index f55fd5a8cc..3358101d6b 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIPickerViewRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIPickerViewRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UIPickerViewRecorderTests: XCTestCase { - private let recorder = UIPickerViewRecorder() + private let recorder = UIPickerViewRecorder(identifier: UUID()) private let picker = UIPickerView() private var viewAttributes: ViewAttributes = .mockAny() diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorderTests.swift index 0babd24dc6..9380283805 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIProgressViewRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UIProgressViewRecorderTests: XCTestCase { - private let recorder = UIProgressViewRecorder() + private let recorder = UIProgressViewRecorder(identifier: UUID()) private let progressView = UIProgressView() private var viewAttributes: ViewAttributes = .mockAny() diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISegmentRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISegmentRecorderTests.swift index f016bf2a26..ab6d07466d 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISegmentRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISegmentRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UISegmentRecorderTests: XCTestCase { - private let recorder = UISegmentRecorder() + private let recorder = UISegmentRecorder(identifier: UUID()) private let segment = UISegmentedControl(items: ["first", "second", "third"]) private var viewAttributes: ViewAttributes = .mockAny() diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISliderRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISliderRecorderTests.swift index 567a7fde3e..dd70270473 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISliderRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISliderRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UISliderRecorderTests: XCTestCase { - private let recorder = UISliderRecorder() + private let recorder = UISliderRecorder(identifier: UUID()) private let slider = UISlider() private var viewAttributes: ViewAttributes = .mockAny() diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIStepperRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIStepperRecorderTests.swift index c24cc0cebc..0e05dbf04d 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIStepperRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIStepperRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UIStepperRecorderTests: XCTestCase { - private let recorder = UIStepperRecorder() + private let recorder = UIStepperRecorder(identifier: UUID()) private let stepper = UIStepper() /// `ViewAttributes` simulating common attributes of switch's `UIView`. private var viewAttributes: ViewAttributes = .mockAny() diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISwitchRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISwitchRecorderTests.swift index 666d0bde35..a5e6a359e2 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISwitchRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UISwitchRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UISwitchRecorderTests: XCTestCase { - private let recorder = UISwitchRecorder() + private let recorder = UISwitchRecorder(identifier: UUID()) /// The label under test. private let `switch` = UISwitch() /// `ViewAttributes` simulating common attributes of switch's `UIView`. diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorderTests.swift index ed8410e9a4..8786502bc1 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITabBarRecorderTests.swift @@ -6,11 +6,13 @@ #if os(iOS) import XCTest +import TestUtilities + @_spi(Internal) @testable import DatadogSessionReplay class UITabBarRecorderTests: XCTestCase { - private let recorder = UITabBarRecorder() + private let recorder = UITabBarRecorder(identifier: UUID()) func testWhenViewIsOfExpectedType() throws { // When @@ -31,5 +33,23 @@ class UITabBarRecorderTests: XCTestCase { // Then XCTAssertNil(recorder.semantics(of: view, with: .mockAny(), in: .mockAny())) } + + func testWhenRecordingSubviewTwice() { + // Given + let tabBar = UITabBar.mock(withFixture: .visible(.someAppearance)) + tabBar.items = [UITabBarItem(title: "first", image: UIImage(), tag: 0)] + let viewAttributes = ViewAttributes(frameInRootView: tabBar.frame, view: tabBar) + + // When + let semantics1 = recorder.semantics(of: tabBar, with: viewAttributes, in: .mockAny()) + let semantics2 = recorder.semantics(of: tabBar, with: viewAttributes, in: .mockAny()) + + let builder = SessionReplayWireframesBuilder() + let wireframes1 = semantics1?.nodes.flatMap { $0.wireframesBuilder.buildWireframes(with: builder) } + let wireframes2 = semantics2?.nodes.flatMap { $0.wireframesBuilder.buildWireframes(with: builder) } + + // Then + DDAssertReflectionEqual(wireframes1, wireframes2) + } } #endif diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextFieldRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextFieldRecorderTests.swift index 948c017048..8a229e6ccb 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextFieldRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextFieldRecorderTests.swift @@ -12,7 +12,7 @@ import TestUtilities // swiftlint:disable opening_brace class UITextFieldRecorderTests: XCTestCase { - private let recorder = UITextFieldRecorder() + private let recorder = UITextFieldRecorder(identifier: UUID()) /// The label under test. private let textField = UITextField(frame: .mockAny()) /// `ViewAttributes` simulating common attributes of text field's `UIView`. diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextViewRecorderTests.swift index 5030b7feed..b8bbc78bf8 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextViewRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UITextViewRecorderTests.swift @@ -12,7 +12,7 @@ import XCTest // swiftlint:disable opening_brace class UITextViewRecorderTests: XCTestCase { - private let recorder = UITextViewRecorder() + private let recorder = UITextViewRecorder(identifier: UUID()) /// The label under test. private let textView = UITextView() /// `ViewAttributes` simulating common attributes of text view's `UIView`. diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIViewRecorderTests.swift index 151851f188..0ffa2e0ccf 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIViewRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UIViewRecorderTests.swift @@ -10,7 +10,7 @@ import XCTest @testable import DatadogSessionReplay class UIViewRecorderTests: XCTestCase { - private let recorder = UIViewRecorder() + private let recorder = UIViewRecorder(identifier: UUID()) /// The view under test. private let view = UIView() /// `ViewAttributes` simulating common attributes of the view. diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorderTests.swift index 769b18fb4c..3ca73fd745 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/UnsupportedViewRecorderTests.swift @@ -14,7 +14,7 @@ import SafariServices @available(iOS 13.0, *) class UnsupportedViewRecorderTests: XCTestCase { - private let recorder = UnsupportedViewRecorder() + private let recorder = UnsupportedViewRecorder(identifier: UUID()) func testWhenViewIsUnsupportedViewControllersRootView() throws { var context = ViewTreeRecordingContext.mockRandom() diff --git a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorderTests.swift b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorderTests.swift index 92c87c893d..1629e7265e 100644 --- a/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorderTests.swift +++ b/DatadogSessionReplay/Tests/Recorder/ViewTreeSnapshotProducer/ViewTreeSnapshot/NodeRecorders/WKWebViewRecorderTests.swift @@ -13,7 +13,7 @@ import TestUtilities @testable import DatadogSessionReplay class WKWebViewRecorderTests: XCTestCase { - private let recorder = WKWebViewRecorder() + private let recorder = WKWebViewRecorder(identifier: UUID()) /// The web-view under test. private let webView = WKWebView() /// `ViewAttributes` simulating common attributes of web-view's `UIView`. From b2075188ba69e3d62d200a345029d001d818bb64 Mon Sep 17 00:00:00 2001 From: Maciej Burda Date: Thu, 25 Jul 2024 08:58:37 +0100 Subject: [PATCH 40/43] Update CHANGELOG --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 660d5e48ae..631c9951b9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [IMPROVEMENT] Ability to clear feature data storage using `clearAllData` API. See [#1940][] - [IMPROVEMENT] Send memory warning as RUM error. See [#1955][] - [IMPROVEMENT] Decorate network span kind as `client`. See [#1963][] +- [FIX] Fix CPU spike when recording UITabBar using SessionReplay. See [#1967][] # 2.14.1 / 09-07-2024 @@ -727,6 +728,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1940]: https://github.com/DataDog/dd-sdk-ios/pull/1940 [#1955]: https://github.com/DataDog/dd-sdk-ios/pull/1955 [#1963]: https://github.com/DataDog/dd-sdk-ios/pull/1963 +[#1967]: https://github.com/DataDog/dd-sdk-ios/pull/1967 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu From a4c8504886cd92364033e705b099dcf21ed64899 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 25 Jul 2024 10:09:17 +0200 Subject: [PATCH 41/43] Update CHANGELOG.md --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 660d5e48ae..2515cbc358 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,7 @@ - [IMPROVEMENT] Ability to clear feature data storage using `clearAllData` API. See [#1940][] - [IMPROVEMENT] Send memory warning as RUM error. See [#1955][] - [IMPROVEMENT] Decorate network span kind as `client`. See [#1963][] +- [FIX] Fix CPU spikes when Watchdog Terminations tracking is enabled. See [#1968][] # 2.14.1 / 09-07-2024 @@ -727,6 +728,7 @@ Release `2.0` introduces breaking changes. Follow the [Migration Guide](MIGRATIO [#1940]: https://github.com/DataDog/dd-sdk-ios/pull/1940 [#1955]: https://github.com/DataDog/dd-sdk-ios/pull/1955 [#1963]: https://github.com/DataDog/dd-sdk-ios/pull/1963 +[#1968]: https://github.com/DataDog/dd-sdk-ios/pull/1968 [@00fa9a]: https://github.com/00FA9A [@britton-earnin]: https://github.com/Britton-Earnin [@hengyu]: https://github.com/Hengyu From 328891fd3be41986b4585fc3807168a3b0e7a3ca Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 25 Jul 2024 14:57:05 +0200 Subject: [PATCH 42/43] Bumped version to 2.15.0 --- DatadogAlamofireExtension.podspec | 2 +- DatadogCore.podspec | 2 +- DatadogCore/Sources/Versioning.swift | 2 +- DatadogCrashReporting.podspec | 2 +- DatadogInternal.podspec | 2 +- DatadogLogs.podspec | 2 +- DatadogObjc.podspec | 2 +- DatadogRUM.podspec | 2 +- DatadogSDK.podspec | 2 +- DatadogSDKAlamofireExtension.podspec | 2 +- DatadogSDKCrashReporting.podspec | 2 +- DatadogSDKObjc.podspec | 2 +- DatadogSessionReplay.podspec | 2 +- DatadogTrace.podspec | 2 +- DatadogWebViewTracking.podspec | 2 +- TestUtilities.podspec | 2 +- 16 files changed, 16 insertions(+), 16 deletions(-) diff --git a/DatadogAlamofireExtension.podspec b/DatadogAlamofireExtension.podspec index 30294e1b7a..85331f4ad0 100644 --- a/DatadogAlamofireExtension.podspec +++ b/DatadogAlamofireExtension.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogAlamofireExtension" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "An Official Extensions of Datadog Swift SDK for Alamofire." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogCore.podspec b/DatadogCore.podspec index 3901c85564..05149b6f28 100644 --- a/DatadogCore.podspec +++ b/DatadogCore.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogCore" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Official Datadog Swift SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogCore/Sources/Versioning.swift b/DatadogCore/Sources/Versioning.swift index 3298b7561f..98d63c0e19 100644 --- a/DatadogCore/Sources/Versioning.swift +++ b/DatadogCore/Sources/Versioning.swift @@ -1,3 +1,3 @@ // GENERATED FILE: Do not edit directly -internal let __sdkVersion = "2.14.1" +internal let __sdkVersion = "2.15.0" diff --git a/DatadogCrashReporting.podspec b/DatadogCrashReporting.podspec index bdbbddef39..b123502e8b 100644 --- a/DatadogCrashReporting.podspec +++ b/DatadogCrashReporting.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogCrashReporting" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Official Datadog Crash Reporting SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogInternal.podspec b/DatadogInternal.podspec index aa1d57cd42..a5ce0b1709 100644 --- a/DatadogInternal.podspec +++ b/DatadogInternal.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogInternal" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Datadog Internal Package. This module is not for public use." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogLogs.podspec b/DatadogLogs.podspec index 90e881c5e9..eba17710ba 100644 --- a/DatadogLogs.podspec +++ b/DatadogLogs.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogLogs" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Datadog Logs Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogObjc.podspec b/DatadogObjc.podspec index 54e902f055..5ff3db89b7 100644 --- a/DatadogObjc.podspec +++ b/DatadogObjc.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogObjc" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Official Datadog Objective-C SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogRUM.podspec b/DatadogRUM.podspec index 24556cb2cc..318973ebeb 100644 --- a/DatadogRUM.podspec +++ b/DatadogRUM.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogRUM" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Datadog Real User Monitoring Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDK.podspec b/DatadogSDK.podspec index feb61a59df..6a5052c845 100644 --- a/DatadogSDK.podspec +++ b/DatadogSDK.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogSDK" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Official Datadog Swift SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKAlamofireExtension.podspec b/DatadogSDKAlamofireExtension.podspec index c435eea385..3e122a071d 100644 --- a/DatadogSDKAlamofireExtension.podspec +++ b/DatadogSDKAlamofireExtension.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKAlamofireExtension" s.module_name = "DatadogAlamofireExtension" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "An Official Extensions of Datadog Swift SDK for Alamofire." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKCrashReporting.podspec b/DatadogSDKCrashReporting.podspec index a6f95fcc5f..048bf48a1a 100644 --- a/DatadogSDKCrashReporting.podspec +++ b/DatadogSDKCrashReporting.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKCrashReporting" s.module_name = "DatadogCrashReporting" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Official Datadog Crash Reporting SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSDKObjc.podspec b/DatadogSDKObjc.podspec index 669c8dd2c3..3346436810 100644 --- a/DatadogSDKObjc.podspec +++ b/DatadogSDKObjc.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = "DatadogSDKObjc" s.module_name = "DatadogObjc" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Official Datadog Objective-C SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogSessionReplay.podspec b/DatadogSessionReplay.podspec index c641b470f8..efeae4525c 100644 --- a/DatadogSessionReplay.podspec +++ b/DatadogSessionReplay.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogSessionReplay" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Official Datadog Session Replay SDK for iOS." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogTrace.podspec b/DatadogTrace.podspec index 3701cfa92d..69be9b3518 100644 --- a/DatadogTrace.podspec +++ b/DatadogTrace.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogTrace" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Datadog Trace Module." s.homepage = "https://www.datadoghq.com" diff --git a/DatadogWebViewTracking.podspec b/DatadogWebViewTracking.podspec index f14208a069..cc5ecb8f38 100644 --- a/DatadogWebViewTracking.podspec +++ b/DatadogWebViewTracking.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "DatadogWebViewTracking" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Datadog WebView Tracking Module." s.homepage = "https://www.datadoghq.com" diff --git a/TestUtilities.podspec b/TestUtilities.podspec index c909015274..9207ebe004 100644 --- a/TestUtilities.podspec +++ b/TestUtilities.podspec @@ -1,6 +1,6 @@ Pod::Spec.new do |s| s.name = "TestUtilities" - s.version = "2.14.1" + s.version = "2.15.0" s.summary = "Datadog Testing Utilities. This module is for internal testing and should not be published." s.homepage = "https://www.datadoghq.com" From 15e3adae0024866f0f6d3799f1f3e8ce630c8b45 Mon Sep 17 00:00:00 2001 From: Maciek Grzybowski Date: Thu, 25 Jul 2024 14:57:51 +0200 Subject: [PATCH 43/43] Update CHANGELOG.md for 2.15.0 release --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 15b6f945b3..defe97c5a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,7 @@ # Unreleased +# 2.15.0 / 25-07-2024 + - [FEATURE] Enable DatadogCore, DatadogLogs and DatadogTrace to compile on watchOS platform. See [#1918][] (Thanks [@jfiser-paylocity][]) [#1946][] - [IMPROVEMENT] Ability to clear feature data storage using `clearAllData` API. See [#1940][] - [IMPROVEMENT] Send memory warning as RUM error. See [#1955][]