From d810ca809e8db12aae2c97f3a3a756d4b9e3970f Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Tue, 13 Apr 2021 17:03:05 +0100 Subject: [PATCH 1/2] Allow use without AppKit --- Bugsnag.xcodeproj/project.pbxproj | 8 ++++ .../Breadcrumbs/BSGNotificationBreadcrumbs.m | 6 +-- Bugsnag/BugsnagSystemState.m | 4 +- Bugsnag/Client/BugsnagClient.m | 2 +- Bugsnag/Helpers/BSGAppKit.h | 41 +++++++++++++++++ Bugsnag/Helpers/BSGUIKit.h | 1 + .../Source/KSCrash/Recording/BSG_KSCrash.m | 2 +- CHANGELOG.md | 7 +++ Tests/BSGAppKitTests.m | 46 +++++++++++++++++++ 9 files changed, 110 insertions(+), 7 deletions(-) create mode 100644 Bugsnag/Helpers/BSGAppKit.h create mode 100644 Tests/BSGAppKitTests.m diff --git a/Bugsnag.xcodeproj/project.pbxproj b/Bugsnag.xcodeproj/project.pbxproj index 6b9b82173..fe0a9243e 100644 --- a/Bugsnag.xcodeproj/project.pbxproj +++ b/Bugsnag.xcodeproj/project.pbxproj @@ -680,6 +680,7 @@ 01840B7425DC26E200F95648 /* BSGEventUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 01840B6E25DC26E200F95648 /* BSGEventUploader.m */; }; 01840B7525DC26E200F95648 /* BSGEventUploader.m in Sources */ = {isa = PBXBuildFile; fileRef = 01840B6E25DC26E200F95648 /* BSGEventUploader.m */; }; 0187D464255BD7B800C503D9 /* BugsnagApiClientTest.m in Sources */ = {isa = PBXBuildFile; fileRef = CB9103632502320A00E9D1E2 /* BugsnagApiClientTest.m */; }; + 019480D42625F3EB00E833ED /* BSGAppKitTests.m in Sources */ = {isa = PBXBuildFile; fileRef = 019480D32625F3EB00E833ED /* BSGAppKitTests.m */; }; 01B14C56251CE55F00118748 /* report-react-native-promise-rejection.json in Resources */ = {isa = PBXBuildFile; fileRef = 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */; }; 01B14C57251CE55F00118748 /* report-react-native-promise-rejection.json in Resources */ = {isa = PBXBuildFile; fileRef = 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */; }; 01B14C58251CE55F00118748 /* report-react-native-promise-rejection.json in Resources */ = {isa = PBXBuildFile; fileRef = 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */; }; @@ -1323,6 +1324,8 @@ 01937D09257A7ED000F2DE31 /* BugsnagSessionTracker+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagSessionTracker+Private.h"; sourceTree = ""; }; 01937D11257A814D00F2DE31 /* BugsnagMetadata+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagMetadata+Private.h"; sourceTree = ""; }; 01937D2E257A83A900F2DE31 /* BugsnagApp+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagApp+Private.h"; sourceTree = ""; }; + 019480C42625EE9800E833ED /* BSGAppKit.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = BSGAppKit.h; sourceTree = ""; }; + 019480D32625F3EB00E833ED /* BSGAppKitTests.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = BSGAppKitTests.m; sourceTree = ""; }; 0195FC3B256BC81400DE6646 /* BugsnagEvent+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagEvent+Private.h"; sourceTree = ""; }; 0198762E2567D5AB000A7AF3 /* BugsnagStackframe+Private.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "BugsnagStackframe+Private.h"; sourceTree = ""; }; 01B14C55251CE55F00118748 /* report-react-native-promise-rejection.json */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.json; path = "report-react-native-promise-rejection.json"; sourceTree = ""; }; @@ -1699,6 +1702,7 @@ 00AD1C7F24869B0E00A27979 /* Tests */ = { isa = PBXGroup; children = ( + 019480D32625F3EB00E833ED /* BSGAppKitTests.m */, 00896A3F2486DBDD00DC48C2 /* BSGConfigurationBuilderTests.m */, 008966C62486D43600DC48C2 /* BSGConnectivityTest.m */, 01BDB1CE25DEBF4600A91FAF /* BSGEventUploadKSCrashReportOperationTests.m */, @@ -1831,6 +1835,7 @@ 008969142486DAD000DC48C2 /* BSG_RFC3339DateTool.m */, 010FF28225ED2A8D00E4F2B0 /* BSGAppHangDetector.h */, 010FF28325ED2A8D00E4F2B0 /* BSGAppHangDetector.m */, + 019480C42625EE9800E833ED /* BSGAppKit.h */, CBCF77A125010648004AF22A /* BSGJSONSerialization.h */, CBCF77A225010648004AF22A /* BSGJSONSerialization.m */, 008968112486DA5600DC48C2 /* BSGSerialization.h */, @@ -2838,6 +2843,7 @@ 008967132486D43700DC48C2 /* BugsnagEventTests.m in Sources */, 0089675B2486D43700DC48C2 /* BugsnagEnabledBreadcrumbTest.m in Sources */, 008966EC2486D43700DC48C2 /* BugsnagDeviceTest.m in Sources */, + 019480D42625F3EB00E833ED /* BSGAppKitTests.m in Sources */, 008967462486D43700DC48C2 /* BugsnagTests.m in Sources */, 008967A62486D43700DC48C2 /* KSString_Tests.m in Sources */, 004E353D2487B3B8007FBAE4 /* BugsnagSwiftTests.swift in Sources */, @@ -3406,6 +3412,7 @@ 00AD1CBF24869C1200A27979 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_MODULES_AUTOLINK = NO; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; @@ -3428,6 +3435,7 @@ 00AD1CC024869C1200A27979 /* Release */ = { isa = XCBuildConfiguration; buildSettings = { + CLANG_MODULES_AUTOLINK = NO; CODE_SIGN_STYLE = Automatic; COMBINE_HIDPI_IMAGES = YES; DEFINES_MODULE = YES; diff --git a/Bugsnag/Breadcrumbs/BSGNotificationBreadcrumbs.m b/Bugsnag/Breadcrumbs/BSGNotificationBreadcrumbs.m index aeed4a1b4..95e4de234 100644 --- a/Bugsnag/Breadcrumbs/BSGNotificationBreadcrumbs.m +++ b/Bugsnag/Breadcrumbs/BSGNotificationBreadcrumbs.m @@ -15,7 +15,7 @@ #if TARGET_OS_IOS || TARGET_OS_TV #import "BSGUIKit.h" #else -#import +#import "BSGAppKit.h" #endif @@ -37,7 +37,7 @@ - (instancetype)initWithConfiguration:(BugsnagConfiguration *)configuration _configuration = configuration; _notificationCenter = NSNotificationCenter.defaultCenter; #if TARGET_OS_OSX - _workspaceNotificationCenter = NSWorkspace.sharedWorkspace.notificationCenter; + _workspaceNotificationCenter = [NSWORKSPACE sharedWorkspace].notificationCenter; #endif _breadcrumbSink = breadcrumbSink; _notificationNameMap = @{ @@ -277,7 +277,7 @@ - (void)addBreadcrumbForMenuItemNotification:(NSNotification *)notification { #if TARGET_OS_OSX NSMenuItem *menuItem = [[notification userInfo] valueForKey:@"MenuItem"]; [self addBreadcrumbWithType:BSGBreadcrumbTypeState forNotificationName:notification.name metadata: - [menuItem isKindOfClass:[NSMenuItem class]] ? @{BSGKeyAction : menuItem.title} : nil]; + [menuItem isKindOfClass:NSMENUITEM] ? @{BSGKeyAction : menuItem.title} : nil]; #endif } diff --git a/Bugsnag/BugsnagSystemState.m b/Bugsnag/BugsnagSystemState.m index 38b5db6c1..bcc1afafd 100644 --- a/Bugsnag/BugsnagSystemState.m +++ b/Bugsnag/BugsnagSystemState.m @@ -11,7 +11,7 @@ #import "BugsnagSystemState.h" #if TARGET_OS_OSX -#import +#import "BSGAppKit.h" #else #import "BSGUIKit.h" #endif @@ -79,7 +79,7 @@ id blankIfNil(id value) { bool isActive = true; #if TARGET_OS_OSX // MacOS "active" serves the same purpose as "foreground" in iOS - isInForeground = [NSApplication sharedApplication].active; + isInForeground = [NSAPPLICATION sharedApplication].active; #else UIApplicationState appState = [BSG_KSSystemInfo currentAppState]; isInForeground = [BSG_KSSystemInfo isInForeground:appState]; diff --git a/Bugsnag/Client/BugsnagClient.m b/Bugsnag/Client/BugsnagClient.m index 9845271f1..e61640dd1 100644 --- a/Bugsnag/Client/BugsnagClient.m +++ b/Bugsnag/Client/BugsnagClient.m @@ -81,7 +81,7 @@ #if BSG_PLATFORM_IOS #import "BSGUIKit.h" #elif BSG_PLATFORM_OSX -#import +#import "BSGAppKit.h" #endif NSString *const BSTabCrash = @"crash"; diff --git a/Bugsnag/Helpers/BSGAppKit.h b/Bugsnag/Helpers/BSGAppKit.h new file mode 100644 index 000000000..44be5da63 --- /dev/null +++ b/Bugsnag/Helpers/BSGAppKit.h @@ -0,0 +1,41 @@ +// +// BSGAppKit.h +// Bugsnag +// +// Created by Nick Dowell on 13/04/2021. +// Copyright © 2021 Bugsnag Inc. All rights reserved. +// + +#import + +// Daemons and other processes running in non-UI sessions should not link against AppKit. +// These macros exist to allow the use of AppKit without adding a link-time dependency on it. + +// Calling code should be prepared for classes to not be found when AppKit is not linked. +#define NSAPPLICATION NSClassFromString(@"NSApplication") +#define NSMENUITEM NSClassFromString(@"NSMenuItem") +#define NSWORKSPACE NSClassFromString(@"NSWorkspace") + +#define NSApplicationDidBecomeActiveNotification @"NSApplicationDidBecomeActiveNotification" +#define NSApplicationDidBecomeActiveNotification @"NSApplicationDidBecomeActiveNotification" +#define NSApplicationDidFinishLaunchingNotification @"NSApplicationDidFinishLaunchingNotification" +#define NSApplicationDidHideNotification @"NSApplicationDidHideNotification" +#define NSApplicationDidResignActiveNotification @"NSApplicationDidResignActiveNotification" +#define NSApplicationDidResignActiveNotification @"NSApplicationDidResignActiveNotification" +#define NSApplicationDidUnhideNotification @"NSApplicationDidUnhideNotification" +#define NSApplicationWillBecomeActiveNotification @"NSApplicationWillBecomeActiveNotification" +#define NSApplicationWillTerminateNotification @"NSApplicationWillTerminateNotification" +#define NSApplicationWillTerminateNotification @"NSApplicationWillTerminateNotification" +#define NSControlTextDidBeginEditingNotification @"NSControlTextDidBeginEditingNotification" +#define NSControlTextDidEndEditingNotification @"NSControlTextDidEndEditingNotification" +#define NSMenuWillSendActionNotification @"NSMenuWillSendActionNotification" +#define NSTableViewSelectionDidChangeNotification @"NSTableViewSelectionDidChangeNotification" +#define NSUndoManagerDidRedoChangeNotification @"NSUndoManagerDidRedoChangeNotification" +#define NSUndoManagerDidUndoChangeNotification @"NSUndoManagerDidUndoChangeNotification" +#define NSWindowDidBecomeKeyNotification @"NSWindowDidBecomeKeyNotification" +#define NSWindowDidEnterFullScreenNotification @"NSWindowDidEnterFullScreenNotification" +#define NSWindowDidExitFullScreenNotification @"NSWindowDidExitFullScreenNotification" +#define NSWindowWillCloseNotification @"NSWindowWillCloseNotification" +#define NSWindowWillMiniaturizeNotification @"NSWindowWillMiniaturizeNotification" +#define NSWorkspaceScreensDidSleepNotification @"NSWorkspaceScreensDidSleepNotification" +#define NSWorkspaceScreensDidWakeNotification @"NSWorkspaceScreensDidWakeNotification" diff --git a/Bugsnag/Helpers/BSGUIKit.h b/Bugsnag/Helpers/BSGUIKit.h index bbe3eae93..d05f39acb 100644 --- a/Bugsnag/Helpers/BSGUIKit.h +++ b/Bugsnag/Helpers/BSGUIKit.h @@ -11,6 +11,7 @@ // When used in some memory constrained contexts such as a file provider extension, linking to UIKit is problematic. // These macros exist to allow the use of UIKit without adding a link-time dependency on it. +// Calling code should be prepared for classes to not be found when UIKit is not linked. #define UIAPPLICATION NSClassFromString(@"UIApplication") #define UIDEVICE NSClassFromString(@"UIDevice") diff --git a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m index 810b33621..348ff54a8 100644 --- a/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m +++ b/Bugsnag/KSCrash/Source/KSCrash/Recording/BSG_KSCrash.m @@ -47,7 +47,7 @@ #import "BSGUIKit.h" #endif #if TARGET_OS_OSX -#import +#import "BSGAppKit.h" #endif // ============================================================================ diff --git a/CHANGELOG.md b/CHANGELOG.md index 0b9292548..b64cb35ea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,13 @@ Changelog ========= +## TBD + +### Enhancements + +* Bugsnag can now be used without AppKit, allowing use in daemons and other processes running in non-UI sessions. + [#1072](https://github.com/bugsnag/bugsnag-cocoa/pull/1072) + ## 6.8.4 (2021-04-14) ### Enhancements diff --git a/Tests/BSGAppKitTests.m b/Tests/BSGAppKitTests.m new file mode 100644 index 000000000..57b405dba --- /dev/null +++ b/Tests/BSGAppKitTests.m @@ -0,0 +1,46 @@ +// +// BSGAppKitTests.m +// Bugsnag-macOSTests +// +// Created by Nick Dowell on 13/04/2021. +// Copyright © 2021 Bugsnag Inc. All rights reserved. +// + +#import + +@interface BSGAppKitTests : XCTestCase + +@end + +@implementation BSGAppKitTests + +- (void)testNotificationNames { + // The notifier uses hard-coded notification names so that it can avoid linking to AppKit. + // These tests ensure that the hard-coded names in BSGAppKit.h match the SDK. + #define ASSERT_NOTIFICATION_NAME(name) XCTAssertEqualObjects(name, @#name) + ASSERT_NOTIFICATION_NAME(NSApplicationDidBecomeActiveNotification); + ASSERT_NOTIFICATION_NAME(NSApplicationDidBecomeActiveNotification); + ASSERT_NOTIFICATION_NAME(NSApplicationDidFinishLaunchingNotification); + ASSERT_NOTIFICATION_NAME(NSApplicationDidHideNotification); + ASSERT_NOTIFICATION_NAME(NSApplicationDidResignActiveNotification); + ASSERT_NOTIFICATION_NAME(NSApplicationDidResignActiveNotification); + ASSERT_NOTIFICATION_NAME(NSApplicationDidUnhideNotification); + ASSERT_NOTIFICATION_NAME(NSApplicationWillBecomeActiveNotification); + ASSERT_NOTIFICATION_NAME(NSApplicationWillTerminateNotification); + ASSERT_NOTIFICATION_NAME(NSApplicationWillTerminateNotification); + ASSERT_NOTIFICATION_NAME(NSControlTextDidBeginEditingNotification); + ASSERT_NOTIFICATION_NAME(NSControlTextDidEndEditingNotification); + ASSERT_NOTIFICATION_NAME(NSMenuWillSendActionNotification); + ASSERT_NOTIFICATION_NAME(NSTableViewSelectionDidChangeNotification); + ASSERT_NOTIFICATION_NAME(NSUndoManagerDidRedoChangeNotification); + ASSERT_NOTIFICATION_NAME(NSUndoManagerDidUndoChangeNotification); + ASSERT_NOTIFICATION_NAME(NSWindowDidBecomeKeyNotification); + ASSERT_NOTIFICATION_NAME(NSWindowDidEnterFullScreenNotification); + ASSERT_NOTIFICATION_NAME(NSWindowDidExitFullScreenNotification); + ASSERT_NOTIFICATION_NAME(NSWindowWillCloseNotification); + ASSERT_NOTIFICATION_NAME(NSWindowWillMiniaturizeNotification); + ASSERT_NOTIFICATION_NAME(NSWorkspaceScreensDidSleepNotification); + ASSERT_NOTIFICATION_NAME(NSWorkspaceScreensDidWakeNotification); +} + +@end From 0598248ebfe26a02f55dce8c1be8ce4a660532ad Mon Sep 17 00:00:00 2001 From: Nick Dowell Date: Wed, 14 Apr 2021 13:30:56 +0100 Subject: [PATCH 2/2] Fix storage path for processes with no bundleIdentifier --- Bugsnag/Storage/BSGFileLocations.m | 3 ++- Tests/BSGStorageMigratorTests.m | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Bugsnag/Storage/BSGFileLocations.m b/Bugsnag/Storage/BSGFileLocations.m index 22da67349..68693a66d 100644 --- a/Bugsnag/Storage/BSGFileLocations.m +++ b/Bugsnag/Storage/BSGFileLocations.m @@ -42,7 +42,8 @@ static BOOL ensureDirExists(NSString *path) { rootPath = [NSString stringWithFormat:@"%@/com.bugsnag.Bugsnag/%@/%@", url.path, - [NSBundle mainBundle].bundleIdentifier, + // Processes that don't have an Info.plist have no bundleIdentifier + NSBundle.mainBundle.bundleIdentifier ?: NSProcessInfo.processInfo.processName, fsVersion]; // If we can't even create the root dir, all is lost, and no file ops can be allowed. diff --git a/Tests/BSGStorageMigratorTests.m b/Tests/BSGStorageMigratorTests.m index 238753d44..4ec6656cc 100644 --- a/Tests/BSGStorageMigratorTests.m +++ b/Tests/BSGStorageMigratorTests.m @@ -51,7 +51,7 @@ - (NSString *)getV1RootDir { return [NSString stringWithFormat:@"%@/com.bugsnag.Bugsnag/%@/v1", dirs[0], - [NSBundle mainBundle].bundleIdentifier]; + NSBundle.mainBundle.bundleIdentifier ?: NSProcessInfo.processInfo.processName]; } - (NSDictionary *)getDirs {