Skip to content

Commit

Permalink
ref: prevent implicit UIKit linkage (#3175)
Browse files Browse the repository at this point in the history
  • Loading branch information
armcknight committed Oct 13, 2023
1 parent 2affdbd commit 98cca71
Show file tree
Hide file tree
Showing 77 changed files with 1,301 additions and 384 deletions.
20 changes: 20 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -166,3 +166,23 @@ jobs:
- uses: actions/checkout@v4
- run: swift build
shell: sh

check-uikit-linkage-debug:
name: Check UIKit linkage (Debug)
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- name: Build for Debug
run: ./scripts/xcode-test.sh "iOS" "latest" $GITHUB_REF_NAME ci build "iPhone 14" Debug_without_UIKit uikit-check-build
- name: Ensure no UIKit
run: ./scripts/check-uikit-linkage.sh Debug_without_UIKit uikit-check-build

check-uikit-linkage-release:
name: Check UIKit linkage (Release)
runs-on: macos-13
steps:
- uses: actions/checkout@v4
- name: Build for Release
run: ./scripts/xcode-test.sh "iOS" "latest" $GITHUB_REF_NAME ci build "iPhone 14" Release_without_UIKit uikit-check-build
- name: Ensure no UIKit
run: ./scripts/check-uikit-linkage.sh Release_without_UIKit uikit-check-build
4 changes: 2 additions & 2 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -182,14 +182,14 @@ jobs:
# We split building and running tests in two steps so we know how long running the tests takes.
- name: Build tests
id: build_tests
run: ./scripts/xcode-test.sh ${{matrix.platform}} ${{matrix.test-destination-os}} $GITHUB_REF_NAME ci build-for-testing "${{matrix.device}}"
run: ./scripts/xcode-test.sh ${{matrix.platform}} ${{matrix.test-destination-os}} $GITHUB_REF_NAME ci build-for-testing "${{matrix.device}}" TestCI

- name: Run tests
# We call a script with the platform so the destination
# passed to xcodebuild doesn't end up in the job name,
# because GitHub Actions don't provide an easy way of
# manipulating string in expressions.
run: ./scripts/xcode-test.sh ${{matrix.platform}} ${{matrix.test-destination-os}} $GITHUB_REF_NAME ci test-without-building "${{matrix.device}}"
run: ./scripts/xcode-test.sh ${{matrix.platform}} ${{matrix.test-destination-os}} $GITHUB_REF_NAME ci test-without-building "${{matrix.device}}" TestCI

- name: Slowest Tests
if: ${{ always() }}
Expand Down
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

### Features

- Sentry can now be used without linking UIKit; this is helpful for using the SDK in certain app extension contexts (#3175)
- GA of MetricKit integration (#3340)

Once enabled, this feature subscribes to [MetricKit's](https://developer.apple.com/documentation/metrickit) [MXDiagnosticPayload](https://developer.apple.com/documentation/metrickit/mxdiagnosticpayload) data, converts it to events, and sends it to Sentry.
Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ GIT-REF := $(shell git rev-parse --abbrev-ref HEAD)

test:
@echo "--> Running all tests"
./scripts/xcode-test.sh iOS latest $(GIT-REF) YES
./scripts/xcode-test.sh iOS latest $(GIT-REF) YES test Test
./scripts/xcode-slowest-tests.sh
.PHONY: test

Expand Down
1 change: 1 addition & 0 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ let package = Package(
"SentryCrash/"
],
publicHeadersPath: "Sentry/Public/",
cSettings: [.unsafeFlags(["-DSENTRY_UIKIT_LINKED=1"])],
cxxSettings: [
.define("GCC_ENABLE_CPP_EXCEPTIONS", to: "YES"),
.headerSearchPath("Sentry/include"),
Expand Down
2 changes: 1 addition & 1 deletion Samples/iOS-Swift/iOS-Swift/ExtraViewController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ class ExtraViewController: UIViewController {
}

SentrySDK.reportFullyDisplayed()

Timer.scheduledTimer(withTimeInterval: 0.5, repeats: true) { _ in
self.framesLabel?.text = "Frames Total:\(PrivateSentrySDKOnly.currentScreenFrames.total) Slow:\(PrivateSentrySDKOnly.currentScreenFrames.slow) Frozen:\(PrivateSentrySDKOnly.currentScreenFrames.frozen)"
}
Expand Down
3 changes: 2 additions & 1 deletion Sentry.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ Pod::Spec.new do |s|
s.pod_target_xcconfig = {
'GCC_ENABLE_CPP_EXCEPTIONS' => 'YES',
'CLANG_CXX_LANGUAGE_STANDARD' => 'c++14',
'CLANG_CXX_LIBRARY' => 'libc++'
'CLANG_CXX_LIBRARY' => 'libc++',
'GCC_PREPROCESSOR_DEFINITIONS' => 'SENTRY_UIKIT_LINKED=1'
}
s.watchos.pod_target_xcconfig = {
'OTHER_LDFLAGS' => '$(inherited) -framework WatchKit'
Expand Down
686 changes: 664 additions & 22 deletions Sentry.xcodeproj/project.pbxproj

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions Sentry.xcodeproj/xcshareddata/xcschemes/Sentry.xcscheme
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,9 @@
<Test
Identifier = "SentrySubClassFinderTests/testActOnSubclassesOfViewController()">
</Test>
<Test
Identifier = "SentryThreadInspectorTests/testStacktraceHasFrames_forEveryThread()">
</Test>
</SkippedTests>
</TestableReference>
<TestableReference
Expand Down
6 changes: 6 additions & 0 deletions SentryTestUtils/SentryTestUtils-ObjC-BridgingHeader.h
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
#import "SentryDefines.h"

#if TARGET_OS_IOS || TARGET_OS_TV
# define SENTRY_UIKIT_AVAILABLE 1
#else
# define SENTRY_UIKIT_AVAILABLE 0
#endif

#if SENTRY_HAS_UIKIT
# import "SentryAppStartTracker.h"
# import "SentryDisplayLinkWrapper.h"
Expand Down
47 changes: 42 additions & 5 deletions Sources/Sentry/PrivateSentrySDKOnly.mm
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ @implementation PrivateSentrySDKOnly
static BOOL _appStartMeasurementHybridSDKMode = NO;
#if SENTRY_HAS_UIKIT
static BOOL _framesTrackingMeasurementHybridSDKMode = NO;
#endif
#endif // SENTRY_HAS_UIKIT

+ (void)storeEnvelope:(SentryEnvelope *)envelope
{
Expand Down Expand Up @@ -157,40 +157,77 @@ + (void)discardProfilerForTrace:(SentryId *)traceId;

#endif // SENTRY_TARGET_PROFILING_SUPPORTED

#if SENTRY_HAS_UIKIT

+ (BOOL)framesTrackingMeasurementHybridSDKMode
{
#if SENTRY_HAS_UIKIT
return _framesTrackingMeasurementHybridSDKMode;
#else
SENTRY_LOG_DEBUG(@"PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode only works with "
@"UIKit enabled. Ensure you're "
@"using the right configuration of Sentry that links UIKit.");
return NO;
#endif // SENTRY_HAS_UIKIT
}

+ (void)setFramesTrackingMeasurementHybridSDKMode:(BOOL)framesTrackingMeasurementHybridSDKMode
{
#if SENTRY_HAS_UIKIT
_framesTrackingMeasurementHybridSDKMode = framesTrackingMeasurementHybridSDKMode;
#else
SENTRY_LOG_DEBUG(@"PrivateSentrySDKOnly.framesTrackingMeasurementHybridSDKMode only works with "
@"UIKit enabled. Ensure you're "
@"using the right configuration of Sentry that links UIKit.");
#endif // SENTRY_HAS_UIKIT
}

+ (BOOL)isFramesTrackingRunning
{
#if SENTRY_HAS_UIKIT
return SentryDependencyContainer.sharedInstance.framesTracker.isRunning;
#else
SENTRY_LOG_DEBUG(@"PrivateSentrySDKOnly.isFramesTrackingRunning only works with UIKit enabled. "
@"Ensure you're "
@"using the right configuration of Sentry that links UIKit.");
return NO;
#endif // SENTRY_HAS_UIKIT
}

+ (SentryScreenFrames *)currentScreenFrames
{
#if SENTRY_HAS_UIKIT
return SentryDependencyContainer.sharedInstance.framesTracker.currentFrames;
#else
SENTRY_LOG_DEBUG(
@"PrivateSentrySDKOnly.currentScreenFrames only works with UIKit enabled. Ensure you're "
@"using the right configuration of Sentry that links UIKit.");
return nil;
#endif // SENTRY_HAS_UIKIT
}

+ (NSArray<NSData *> *)captureScreenshots
{
#if SENTRY_HAS_UIKIT
return [SentryDependencyContainer.sharedInstance.screenshot takeScreenshots];
#else
SENTRY_LOG_DEBUG(
@"PrivateSentrySDKOnly.captureScreenshots only works with UIKit enabled. Ensure you're "
@"using the right configuration of Sentry that links UIKit.");
return nil;
#endif // SENTRY_HAS_UIKIT
}

+ (NSData *)captureViewHierarchy
{
#if SENTRY_HAS_UIKIT
return [SentryDependencyContainer.sharedInstance.viewHierarchy fetchViewHierarchy];
#else
SENTRY_LOG_DEBUG(
@"PrivateSentrySDKOnly.captureViewHierarchy only works with UIKit enabled. Ensure you're "
@"using the right configuration of Sentry that links UIKit.");
return nil;
#endif // SENTRY_HAS_UIKIT
}

#endif

+ (SentryUser *)userWithDictionary:(NSDictionary *)dictionary
{
return [[SentryUser alloc] initWithDictionary:dictionary];
Expand Down
18 changes: 17 additions & 1 deletion Sources/Sentry/Public/SentryDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,13 +6,29 @@
# define SENTRY_EXTERN extern __attribute__((visibility("default")))
#endif

// SENTRY_UIKIT_AVAILABLE basically means: are we on a platform where we can link UIKit?
#if TARGET_OS_IOS || TARGET_OS_TV
# define SENTRY_UIKIT_AVAILABLE 1
#else
# define SENTRY_UIKIT_AVAILABLE 0
#endif

// SENTRY_HAS_UIKIT means we're on a platform that can link UIKit and we're building a configuration
// that will allow it to be autolinked. SENTRY_UIKIT_LINKED is set in GCC_PREPROCESSOR_DEFINITIONS
// for configurations that we will allow to link UIKit by setting CLANG_MODULES_AUTOLINK to YES.
#if SENTRY_UIKIT_AVAILABLE && SENTRY_UIKIT_LINKED
# define SENTRY_HAS_UIKIT 1
#else
# define SENTRY_HAS_UIKIT 0
#endif

#if TARGET_OS_IOS || TARGET_OS_OSX || TARGET_OS_MACCATALYST
#if TARGET_OS_OSX || TARGET_OS_MACCATALYST
# define SENTRY_TARGET_MACOS 1
#else
# define SENTRY_TARGET_MACOS 0
#endif

#if TARGET_OS_IOS || SENTRY_TARGET_MACOS
# define SENTRY_HAS_METRIC_KIT 1
#else
# define SENTRY_HAS_METRIC_KIT 0
Expand Down
18 changes: 14 additions & 4 deletions Sources/Sentry/Public/SentryOptions.h
Original file line number Diff line number Diff line change
Expand Up @@ -199,15 +199,19 @@ NS_SWIFT_NAME(Options)
*/
@property (nonatomic) SentryScope * (^initialScope)(SentryScope *);

#if SENTRY_HAS_UIKIT
#if SENTRY_UIKIT_AVAILABLE
/**
* When enabled, the SDK tracks performance for UIViewController subclasses.
* @warning This feature is not available in @c Debug_without_UIKit and @c Release_without_UIKit
* configurations even when targeting iOS or tvOS platforms.
* @note The default is @c YES .
*/
@property (nonatomic, assign) BOOL enableUIViewControllerTracing;

/**
* Automatically attaches a screenshot when capturing an error or exception.
* @warning This feature is not available in @c Debug_without_UIKit and @c Release_without_UIKit
* configurations even when targeting iOS or tvOS platforms.
* @note Default value is @c NO .
*/
@property (nonatomic, assign) BOOL attachScreenshot;
Expand All @@ -216,24 +220,29 @@ NS_SWIFT_NAME(Options)
* @warning This is an experimental feature and may still have bugs.
* @brief Automatically attaches a textual representation of the view hierarchy when capturing an
* error event.
* @warning This feature is not available in @c Debug_without_UIKit and @c Release_without_UIKit
* configurations even when targeting iOS or tvOS platforms.
* @note Default value is @c NO .
*/
@property (nonatomic, assign) BOOL attachViewHierarchy;

/**
* When enabled, the SDK creates transactions for UI events like buttons clicks, switch toggles,
* and other ui elements that uses UIControl @c sendAction:to:forEvent:
* @warning This feature is not available in @c Debug_without_UIKit and @c Release_without_UIKit
* configurations even when targeting iOS or tvOS platforms.
* @note Default value is @c YES .
*/
@property (nonatomic, assign) BOOL enableUserInteractionTracing;

/**
* How long an idle transaction waits for new children after all its child spans finished. Only UI
* event transactions are idle transactions.
* @warning This feature is not available in @c Debug_without_UIKit and @c Release_without_UIKit
* configurations even when targeting iOS or tvOS platforms.
* @note The default is 3 seconds.
*/
@property (nonatomic, assign) NSTimeInterval idleTimeout;

/**
* @warning This is an experimental feature and may still have bugs.
* @brief Report pre-warmed app starts by dropping the first app start spans if pre-warming paused
Expand All @@ -242,11 +251,12 @@ NS_SWIFT_NAME(Options)
* @note You can filter for different app start types in Discover with
* @c app_start_type:cold.prewarmed ,
* @c app_start_type:warm.prewarmed , @c app_start_type:cold , and @c app_start_type:warm .
* @warning This feature is not available in @c Debug_without_UIKit and @c Release_without_UIKit
* configurations even when targeting iOS or tvOS platforms.
* @note Default value is @c NO .
*/
@property (nonatomic, assign) BOOL enablePreWarmedAppStartTracing;

#endif // SENTRY_HAS_UIKIT
#endif // SENTRY_UIKIT_AVAILABLE

/**
* When enabled, the SDK tracks performance for HTTP requests if auto performance tracking and
Expand Down
28 changes: 26 additions & 2 deletions Sources/Sentry/SentryAppStartMeasurement.m
Original file line number Diff line number Diff line change
@@ -1,25 +1,43 @@
#import "SentryAppStartMeasurement.h"

#if SENTRY_HAS_UIKIT
#if SENTRY_UIKIT_AVAILABLE

# import "NSDate+SentryExtras.h"
# import "SentryLog.h"
# import <Foundation/Foundation.h>

@implementation SentryAppStartMeasurement
# if SENTRY_HAS_UIKIT
{
SentryAppStartType _type;
BOOL _isPreWarmed;
NSTimeInterval _duration;
NSDate *_appStartTimestamp;
NSDate *_runtimeInitTimestamp;
NSDate *_moduleInitializationTimestamp;
NSDate *_didFinishLaunchingTimestamp;
}
# endif // SENTRY_HAS_UIKIT

- (instancetype)initWithType:(SentryAppStartType)type
appStartTimestamp:(NSDate *)appStartTimestamp
duration:(NSTimeInterval)duration
runtimeInitTimestamp:(NSDate *)runtimeInitTimestamp
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp
{
# if SENTRY_HAS_UIKIT
return [self initWithType:type
isPreWarmed:NO
appStartTimestamp:appStartTimestamp
duration:duration
runtimeInitTimestamp:runtimeInitTimestamp
moduleInitializationTimestamp:[NSDate dateWithTimeIntervalSince1970:0]
didFinishLaunchingTimestamp:didFinishLaunchingTimestamp];
# else
SENTRY_LOG_DEBUG(@"SentryAppStartMeasurement only works with UIKit enabled. Ensure you're "
@"using the right configuration of Sentry that links UIKit.");
return nil;
# endif // SENTRY_HAS_UIKIT
}

- (instancetype)initWithType:(SentryAppStartType)type
Expand All @@ -30,6 +48,7 @@ - (instancetype)initWithType:(SentryAppStartType)type
moduleInitializationTimestamp:(NSDate *)moduleInitializationTimestamp
didFinishLaunchingTimestamp:(NSDate *)didFinishLaunchingTimestamp
{
# if SENTRY_HAS_UIKIT
if (self = [super init]) {
_type = type;
_isPreWarmed = isPreWarmed;
Expand All @@ -41,8 +60,13 @@ - (instancetype)initWithType:(SentryAppStartType)type
}

return self;
# else
SENTRY_LOG_DEBUG(@"SentryAppStartMeasurement only works with UIKit enabled. Ensure you're "
@"using the right configuration of Sentry that links UIKit.");
return nil;
# endif // SENTRY_HAS_UIKIT
}

@end

#endif // SENTRY_HAS_UIKIT
#endif // SENTRY_UIKIT_AVAILABLE
1 change: 1 addition & 0 deletions Sources/Sentry/SentryAppStartTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

# import "SentryAppStartMeasurement.h"
# import "SentryAppStateManager.h"
# import "SentryDefines.h"
# import "SentryLog.h"
# import "SentrySysctl.h"
# import <Foundation/Foundation.h>
Expand Down
5 changes: 3 additions & 2 deletions Sources/Sentry/SentryAppStateManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -179,10 +179,11 @@ - (SentryAppState *)buildCurrentAppState
// Is the current process being traced or not? If it is a debugger is attached.
bool isDebugging = self.crashWrapper.isBeingTraced;

NSString *vendorId = [UIDevice.currentDevice.identifierForVendor UUIDString];
UIDevice *device = [UIDevice currentDevice];
NSString *vendorId = [device.identifierForVendor UUIDString];

return [[SentryAppState alloc] initWithReleaseName:self.options.releaseName
osVersion:UIDevice.currentDevice.systemVersion
osVersion:device.systemVersion
vendorId:vendorId
isDebugging:isDebugging
systemBootTimestamp:SentryDependencyContainer.sharedInstance
Expand Down

0 comments on commit 98cca71

Please sign in to comment.