Skip to content
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions packages/dart/lib/src/protocol/sdk_version.dart
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,13 @@ class SdkVersion {
required this.name,
required this.version,
List<String>? integrations,
List<String>? features,
List<SentryPackage>? packages,
this.unknown,
}) :
// List.from prevents from having immutable lists
_integrations = List.from(integrations ?? []),
_features = List.from(features ?? []),
_packages = List.from(packages ?? []);

/// The name of the SDK.
Expand All @@ -57,6 +59,11 @@ class SdkVersion {
/// An immutable list of integrations enabled in the SDK that created the [Event].
List<String> get integrations => List.unmodifiable(_integrations);

List<String> _features;

/// An immutable list of features enabled in the SDK.
List<String> get features => List.unmodifiable(_features);

List<SentryPackage> _packages;

/// An immutable list of packages that compose this SDK.
Expand All @@ -70,6 +77,7 @@ class SdkVersion {
final json = AccessAwareMap(data);
final packagesJson = json['packages'] as List<dynamic>?;
final integrationsJson = json['integrations'] as List<dynamic>?;
final featuresJson = json['features'] as List<dynamic>?;

return SdkVersion(
name: json['name'],
Expand All @@ -78,6 +86,7 @@ class SdkVersion {
?.map((e) => SentryPackage.fromJson(e as Map<String, dynamic>))
.toList(),
integrations: integrationsJson?.map((e) => e as String).toList(),
features: featuresJson?.map((e) => e as String).toList(),
unknown: json.notAccessed(),
);
}
Expand All @@ -91,6 +100,7 @@ class SdkVersion {
if (packages.isNotEmpty)
'packages': packages.map((p) => p.toJson()).toList(growable: false),
if (integrations.isNotEmpty) 'integrations': integrations,
if (features.isNotEmpty) 'features': features,
};
}

Expand All @@ -114,17 +124,27 @@ class SdkVersion {
_integrations.add(integration);
}

// Adds a feature if not already added
void addFeature(String feature) {
if (_features.contains(feature)) {
return;
}
_features.add(feature);
}

@Deprecated('Assign values directly to the instance.')
SdkVersion copyWith({
String? name,
String? version,
List<String>? integrations,
List<String>? features,
List<SentryPackage>? packages,
}) =>
SdkVersion(
name: name ?? this.name,
version: version ?? this.version,
integrations: integrations ?? _integrations,
features: features ?? _features,
packages: packages ?? _packages,
unknown: unknown,
);
Expand Down
20 changes: 20 additions & 0 deletions packages/dart/test/protocol/sdk_version_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,24 @@ void main() {
expect(1, sut.packages.length);
});
});

group('addFeature', () {
final fixture = Fixture();

test('add feature if not already present', () {
final sut = fixture.getSut();
sut.addFeature('newFeature');

expect(sut.features.last, 'newFeature');
});

test('does not add feature if already present', () {
final sut = fixture.getSut();
sut.addFeature('testFeature');

expect(sut.features.where((f) => f == 'testFeature').length, 1);
});
});
}

class Fixture {
Expand All @@ -95,6 +113,7 @@ class Fixture {
'version': 'version',
}
],
'features': ['testFeature'],
};

Fixture() {
Expand All @@ -106,6 +125,7 @@ class Fixture {
version: 'version',
integrations: ['test'],
packages: [SentryPackage('name', 'version')],
features: ['testFeature'],
unknown: testUnknown,
);
}
3 changes: 3 additions & 0 deletions packages/flutter/ios/sentry_flutter/Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ let package = Package(
dependencies: [
"sentry_flutter_objc",
.product(name: "Sentry", package: "sentry-cocoa")
],
swiftSettings: [
.define("SENTRY_FLUTTER_SPM")
]
),
// SPM does not support mixed-language targets, so we need to move the ObjC files into a separate one
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -213,6 +213,12 @@
infos["integrations"] = integrations.filter { $0 != "SentrySessionReplayIntegration" }
}

#if SENTRY_FLUTTER_SPM
infos["features"] = ["SwiftPackageManager"]
#else
infos["features"] = ["Cocoapods"]
Comment thread
buenaflor marked this conversation as resolved.
Outdated
Comment thread
buenaflor marked this conversation as resolved.
Outdated
#endif

let deviceStr = "device"
let appStr = "app"
if let extraContext = PrivateSentrySDKOnly.getExtraContext() as? [String: Any] {
Expand Down Expand Up @@ -241,8 +247,13 @@

// Not reading the name from PrivateSentrySDKOnly.getSdkName because
// this is added as a package and packages should follow the sentry-release-registry format
#if SENTRY_FLUTTER_SPM
infos["package"] = ["version": PrivateSentrySDKOnly.getSdkVersionString(),
"sdk_name": "spm:sentry-cocoa"]
#else
infos["package"] = ["version": PrivateSentrySDKOnly.getSdkVersionString(),
"sdk_name": "cocoapods:sentry-cocoa"]
#endif

result(infos)
}
Expand Down Expand Up @@ -276,7 +287,7 @@
result(debugImages.map { $0.serialize() })
}

private func initNativeSdk(_ call: FlutterMethodCall, result: @escaping FlutterResult) {

Check failure on line 290 in packages/flutter/ios/sentry_flutter/Sources/sentry_flutter/SentryFlutterPlugin.swift

View workflow job for this annotation

GitHub Actions / swift-lint

Cyclomatic Complexity Violation: Function should have complexity 10 or less; currently complexity is 11 (cyclomatic_complexity)
guard let arguments = call.arguments as? [String: Any], !arguments.isEmpty else {
print("Arguments is null or empty")
result(FlutterError(code: "4", message: "Arguments is null or empty", details: nil))
Expand Down Expand Up @@ -319,6 +330,13 @@
sdk["integrations"] = integrations
}
}
if let features = flutterSdk!["features"] as? [String] {
if let sdkFeatures = sdk["features"] as? [String] {
sdk["features"] = sdkFeatures + features

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we even need this, but shouldn't we do de-duplication here as well?

@buenaflor buenaflor Feb 11, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we can, I just didnt add it because theres almost no chance for a dupe to happen - imo we don't really need it

} else {
sdk["features"] = features
}
Comment thread
buenaflor marked this conversation as resolved.
}
event.sdk = sdk
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,18 @@ class _LoadContextsIntegrationEventProcessor implements EventProcessor {
event.sdk = sdk;
}

final featuresList = infos['features'] as List?;
if (featuresList != null && featuresList.isNotEmpty) {
final features = List<String>.from(featuresList);
final sdk = event.sdk ?? _options.sdk;

for (final feature in features) {
sdk.addFeature(feature);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Here should the merged de-duplication be done, right?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah when adding/merging to the dart features it already dedupes (addFeature only adds if unique)

}

event.sdk = sdk;
}

final packageMap = infos['package'] as Map?;
if (packageMap != null && packageMap.isNotEmpty) {
final package = Map<String, String>.from(packageMap);
Expand Down
1 change: 1 addition & 0 deletions packages/flutter/lib/src/sentry_flutter.dart
Original file line number Diff line number Diff line change
Expand Up @@ -260,6 +260,7 @@ mixin SentryFlutter {
version: sdkVersion,
integrations: options.sdk.integrations,
packages: options.sdk.packages,
features: options.sdk.features,
);
sdk.addPackage('pub:sentry_flutter', sdkVersion);
options.sdk = sdk;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'fixture.dart';
void main() {
final defaultContexts = {
'integrations': ['NativeIntegration'],
'features': ['SwiftPackageManager'],
'package': {'sdk_name': 'native-package', 'version': '1.0'},
'contexts': {
'device': {
Expand Down Expand Up @@ -291,6 +292,39 @@ void main() {
);
});

group('features', () {
test('merges features from native into sdk', () async {
mockLoadContexts();
await fixture.registerIntegration();

final e = getEvent();
final event =
await fixture.options.eventProcessors.first.apply(e, Hint());

expect(event?.sdk?.features.contains('SwiftPackageManager'), true);
});

test('does not duplicate feature if already present', () async {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

馃憤

mockLoadContexts({
'features': ['EventFeature']
});
await fixture.registerIntegration();

final sdk = getSdkVersion();
sdk.addFeature('EventFeature');
final e = getEvent(sdk: sdk);
final event =
await fixture.options.eventProcessors.first.apply(e, Hint());

expect(
event?.sdk?.features
.where((f) => f == 'EventFeature')
.length,
1,
);
});
});

group('breadcrumbs', () {
test('takes breadcrumbs from native if scope sync is enabled', () async {
await fixture.registerIntegration();
Expand Down
Loading