Skip to content
Merged
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
91 commits
Select commit Hold shift + click to select a range
bfd77cd
Initial impl
buenaflor Aug 8, 2025
99dee07
Initial impl
buenaflor Aug 8, 2025
21ba340
Initial impl
buenaflor Aug 8, 2025
4449ef8
Update
buenaflor Aug 11, 2025
7fed2aa
Update
buenaflor Aug 11, 2025
46729fd
Fix
buenaflor Aug 11, 2025
e20751a
Merge branch 'feat/find-relevant-debug-files' into feat/find-mapping-…
buenaflor Aug 11, 2025
3272add
Update
buenaflor Aug 11, 2025
a25d75a
Update
buenaflor Aug 12, 2025
e69117e
Update
buenaflor Aug 12, 2025
45f7bc2
Update
buenaflor Aug 12, 2025
e97c0df
Update
buenaflor Aug 12, 2025
8615400
Update
buenaflor Aug 12, 2025
c777be7
Update
buenaflor Aug 12, 2025
8c97581
Update
buenaflor Aug 12, 2025
0502f23
Update
buenaflor Aug 12, 2025
4524772
Optimize
buenaflor Aug 12, 2025
396507c
Update
buenaflor Aug 12, 2025
1eea0d8
Update
buenaflor Aug 12, 2025
c420e72
Remove unnecessary file check
buenaflor Aug 12, 2025
0cd79e7
Review
buenaflor Aug 12, 2025
a49d2de
Add missing test
buenaflor Aug 12, 2025
82f1afc
Update tests
buenaflor Aug 12, 2025
035af1b
Update tests
buenaflor Aug 12, 2025
b4d9a97
Update tests
buenaflor Aug 12, 2025
832e5b8
Update
buenaflor Aug 12, 2025
4da7432
Update
buenaflor Aug 12, 2025
5eff2db
Update
buenaflor Aug 12, 2025
90a99cb
Update
buenaflor Aug 12, 2025
48305ef
Updaet
buenaflor Aug 12, 2025
ad59fb8
Update
buenaflor Aug 12, 2025
c9092b5
Update
buenaflor Aug 12, 2025
6e7b946
Update
buenaflor Aug 12, 2025
4645b04
Update
buenaflor Aug 12, 2025
a0fb007
Update
buenaflor Aug 12, 2025
c1bdd43
Update
buenaflor Aug 12, 2025
5edd4f7
Update
buenaflor Aug 12, 2025
d5d6490
Update
buenaflor Aug 13, 2025
6cea83c
Update cli_params.dart
buenaflor Aug 13, 2025
610cffe
Update dart_symbol_map.dart
buenaflor Aug 13, 2025
8f224b8
Update tests
buenaflor Aug 13, 2025
cc7514b
Fix
buenaflor Aug 13, 2025
5210a26
Update
buenaflor Aug 13, 2025
f096be1
Update
buenaflor Aug 13, 2025
187f68b
Update
buenaflor Aug 13, 2025
0f930a4
Merge branch 'main' into feat/find-mapping-file
buenaflor Aug 13, 2025
07106f7
Run tests
buenaflor Aug 13, 2025
3f3a278
Add marker
buenaflor Aug 13, 2025
b605d70
Update
buenaflor Aug 13, 2025
ced1eee
Update
buenaflor Aug 13, 2025
897756c
Update
buenaflor Aug 13, 2025
1cb2765
Update
buenaflor Aug 14, 2025
725ea42
Update
buenaflor Aug 14, 2025
78faf96
Update
buenaflor Aug 14, 2025
8a8ee27
Update
buenaflor Aug 14, 2025
440caf0
Update
buenaflor Aug 14, 2025
6d69035
Update
buenaflor Aug 14, 2025
6b6eff0
Update
buenaflor Aug 14, 2025
67a0f17
Update
buenaflor Aug 14, 2025
a2bf91c
Update
buenaflor Aug 14, 2025
90bbaf0
Update
buenaflor Aug 14, 2025
f4e1ccb
Update
buenaflor Aug 14, 2025
aea323e
Update
buenaflor Aug 14, 2025
1dd8c9c
Update
buenaflor Aug 14, 2025
40ddd00
Update
buenaflor Aug 14, 2025
82c6294
Update CHANGELOG
buenaflor Aug 14, 2025
2b63ca8
Update CHANGELOG
buenaflor Aug 14, 2025
089f82e
Update
buenaflor Aug 14, 2025
54dd327
Update
buenaflor Aug 14, 2025
1af9d5b
Update
buenaflor Aug 14, 2025
f0b27b5
Update
buenaflor Aug 14, 2025
9a35bd6
Update
buenaflor Aug 14, 2025
49d12eb
Update
buenaflor Aug 14, 2025
1f31f27
Update
buenaflor Aug 14, 2025
a1eeebe
Update
buenaflor Aug 14, 2025
811a8d4
Update
buenaflor Aug 14, 2025
fba5932
Update docs
buenaflor Aug 14, 2025
777e17d
Fix tests
buenaflor Aug 14, 2025
9db5261
Update CHANGELOG
buenaflor Aug 14, 2025
0f899d5
Update
buenaflor Aug 14, 2025
1ee6f3d
Update test
buenaflor Aug 14, 2025
a79df66
Remove duplicate
buenaflor Aug 14, 2025
3224d42
Cursor review
buenaflor Aug 14, 2025
0ef4ecf
Update marker
buenaflor Aug 14, 2025
3b3af39
Update comment
buenaflor Aug 14, 2025
4a61274
Review
buenaflor Aug 14, 2025
7919595
Update CI
buenaflor Aug 14, 2025
7c56763
Update
buenaflor Aug 14, 2025
f81d281
Update
buenaflor Aug 14, 2025
e67917a
Update
buenaflor Aug 14, 2025
08b9690
Update
buenaflor Aug 14, 2025
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
2 changes: 1 addition & 1 deletion .github/workflows/integration-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ jobs:
distribution: 'temurin'
java-version: '17'

- run: sudo apt-get install ninja-build libgtk-3-dev
- run: sudo apt-get update && sudo apt-get install -y ninja-build libgtk-3-dev
if: runner.os == 'Linux'

- run: (flutter --version)[0] | Out-File flutter.version
Expand Down
130 changes: 77 additions & 53 deletions lib/sentry_dart_plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ import 'package:process/process.dart';
import 'package:sentry_dart_plugin/src/utils/extensions.dart';

import 'src/configuration.dart';
import 'src/utils/flutter_debug_files.dart';
import 'src/utils/dart_symbol_map.dart';
import 'src/utils/injector.dart';
import 'src/utils/log.dart';

Expand All @@ -13,6 +15,8 @@ import 'src/utils/log.dart';
class SentryDartPlugin {
late Configuration _configuration;
final symbolFileRegexp = RegExp(r'[/\\]app[^/\\]+.*\.(dSYM|symbols)$');
// Temporary feature flag: guarded no-op until sentry-cli supports Dart symbol map upload.
final bool _dartSymbolMapUploadEnabled = false;

/// SentryDartPlugin ctor. that inits the injectors
SentryDartPlugin() {
Expand Down Expand Up @@ -86,7 +90,8 @@ class SentryDartPlugin {
_addWait(params);

final fs = injector.get<FileSystem>();
final debugSymbolPaths = _enumerateDebugSymbolPaths(fs);
final debugSymbolPaths =
enumerateDebugSearchRoots(fs: fs, config: _configuration);
await for (final path in debugSymbolPaths) {
if (await fs.directory(path).exists() || await fs.file(path).exists()) {
await _executeAndLog('Failed to upload symbols', [...params, path]);
Expand All @@ -97,59 +102,9 @@ class SentryDartPlugin {
await _executeAndLog('Failed to upload symbols', [...params, path]);
}

Log.taskCompleted(taskName);
}

Stream<String> _enumerateDebugSymbolPaths(FileSystem fs) async* {
final buildDir = _configuration.buildFilesFolder;
final projectRoot = fs.currentDirectory.path;

// Android (apk, appbundle)
yield '$buildDir/app/outputs';
yield '$buildDir/app/intermediates';

// Windows
for (final subdir in ['', '/x64', '/arm64']) {
yield '$buildDir/windows$subdir/runner/Release';
}
// TODO we should delete this once we have windows symbols collected automatically.
// Related to https://github.com/getsentry/sentry-dart-plugin/issues/173
yield 'windows/flutter/ephemeral/flutter_windows.dll.pdb';
await _tryUploadDartSymbolMap();

// Linux
for (final subdir in ['/x64', '/arm64']) {
yield '$buildDir/linux$subdir/release/bundle';
}

// macOS
yield '$buildDir/macos/Build/Products/Release';

// macOS (macOS-framework)
yield '$buildDir/macos/framework/Release';

// iOS
yield '$buildDir/ios/iphoneos/Runner.app';
if (await fs.directory('$buildDir/ios').exists()) {
final regexp = RegExp(r'^Release(-.*)?-iphoneos$');
yield* fs
.directory('$buildDir/ios')
.list()
.where((v) => regexp.hasMatch(v.basename))
.map((e) => e.path);
}

// iOS (ipa)
yield '$buildDir/ios/archive';

// iOS (ios-framework)
yield '$buildDir/ios/framework/Release';

// iOS in Fastlane
if (projectRoot == '/') {
yield 'ios/build';
} else {
yield '$projectRoot/ios/build';
}
Log.taskCompleted(taskName);
}

Future<Set<String>> _enumerateSymbolFiles() async {
Expand Down Expand Up @@ -194,6 +149,75 @@ class SentryDartPlugin {
return params;
}

/// Guarded implementation for uploading Dart symbol map alongside each relevant debug file.
/// Currently a no-op until `_dartSymbolMapUploadEnabled` is flipped to true.
Future<void> _tryUploadDartSymbolMap() async {
if (!_dartSymbolMapUploadEnabled) {
Log.info('Dart symbol map upload is disabled in this version. Skipping.');
return;
}

const taskName = 'uploading Dart symbol map(s)';
Log.startingTask(taskName);

try {
final fs = injector.get<FileSystem>();

// Check for explicitly provided and valid Dart symbol map (wire to config in a follow-up).
final symbolMapPath = await resolveDartSymbolMapPath(
fs: fs,
configuredPath: null,
);
if (symbolMapPath == null) {
Log.warn(
"No 'dart_symbol_map_path' provided. Set --sentry-define --dart_symbol_map_path=/abs/path/to/map to enable Dart obfuscation map usage. Skipping Dart symbol map uploads.",
);
Log.taskCompleted(taskName);
return;
}

final debugFilePaths = await findFlutterRelevantDebugFilePaths(
fs: fs,
config: _configuration,
);

if (debugFilePaths.isEmpty) {
Log.info(
'No Flutter-relevant debug files found for Dart symbol map upload.');
Log.taskCompleted(taskName);
return;
}

for (final debugFilePath in debugFilePaths) {
// Accept both files and directories, mirroring current symbol search behavior.
final isFile = await fs.file(debugFilePath).exists();
final isDir = await fs.directory(debugFilePath).exists();
if (!isFile && !isDir) {
continue;
}

final params = <String>[];
_setUrlAndTokenAndLog(params);
params.add('dart-symbol-map');
params.add('upload');
_addOrgAndProject(params);
_addWait(params);
params.add(symbolMapPath);
params.add(debugFilePath);

await _executeAndLog(
'Failed to upload Dart symbol map for $debugFilePath',
params,
);
}

Log.taskCompleted(taskName);
} catch (e) {
Log.error('Dart symbol map upload failed: $e');
Log.taskCompleted(taskName);
}
}

Future<void> _executeNewRelease(String release) async {
await _executeAndLog('Failed to create a new release',
[..._releasesCliParams(), 'new', release]);
Expand Down
4 changes: 4 additions & 0 deletions lib/src/configuration.dart
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,9 @@ class Configuration {
/// The directory passed to `--split-debug-info`, defaults to '.'
late String symbolsFolder;

/// Explicit path to the Dart obfuscation map file. Optional.
late String? dartSymbolMapPath;

/// The URL prefix, defaults to null
late String? urlPrefix;

Expand Down Expand Up @@ -154,6 +157,7 @@ class Configuration {
final webBuildPath = configValues.webBuildPath ?? 'web';
webBuildFilesFolder = _fs.path.join(buildFilesFolder, webBuildPath);
symbolsFolder = configValues.symbolsPath ?? '.';
dartSymbolMapPath = configValues.dartSymbolMapPath;

project = configValues.project; // or env. var. SENTRY_PROJECT
org = configValues.org; // or env. var. SENTRY_ORG
Expand Down
12 changes: 12 additions & 0 deletions lib/src/configuration_values.dart
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ class ConfigurationValues {
final String? buildPath;
final String? webBuildPath;
final String? symbolsPath;
final String? dartSymbolMapPath;
final String? commits;
final bool? ignoreMissing;
final String? binDir;
Expand All @@ -46,6 +47,7 @@ class ConfigurationValues {
this.buildPath,
this.webBuildPath,
this.symbolsPath,
this.dartSymbolMapPath,
this.commits,
this.ignoreMissing,
this.binDir,
Expand Down Expand Up @@ -99,6 +101,7 @@ class ConfigurationValues {
buildPath: sentryArguments['build_path'],
webBuildPath: sentryArguments['web_build_path'],
symbolsPath: sentryArguments['symbols_path'],
dartSymbolMapPath: sentryArguments['dart_symbol_map_path'],
commits: sentryArguments['commits'],
ignoreMissing: boolFromString(sentryArguments['ignore_missing']),
binDir: sentryArguments['bin_dir'],
Expand Down Expand Up @@ -136,6 +139,7 @@ class ConfigurationValues {
buildPath: configReader.getString('build_path'),
webBuildPath: configReader.getString('web_build_path'),
symbolsPath: configReader.getString('symbols_path'),
dartSymbolMapPath: configReader.getString('dart_symbol_map_path'),
commits: configReader.getString('commits'),
ignoreMissing: configReader.getBool('ignore_missing'),
binDir: configReader.getString('bin_dir'),
Expand All @@ -161,10 +165,15 @@ class ConfigurationValues {
if (envSentryCliCdnUrl?.isEmpty ?? false) {
envSentryCliCdnUrl = null;
}
String? envDartSymbolMapPath = environment['SENTRY_DART_SYMBOL_MAP_PATH'];
if (envDartSymbolMapPath?.isEmpty ?? false) {
envDartSymbolMapPath = null;
}
return ConfigurationValues(
release: envRelease,
dist: envDist,
sentryCliCdnUrl: envSentryCliCdnUrl,
dartSymbolMapPath: envDartSymbolMapPath,
);
}

Expand All @@ -191,6 +200,9 @@ class ConfigurationValues {
buildPath: args.buildPath ?? file.buildPath,
webBuildPath: args.webBuildPath ?? file.webBuildPath,
symbolsPath: args.symbolsPath ?? file.symbolsPath,
dartSymbolMapPath: platformEnv.dartSymbolMapPath ??
args.dartSymbolMapPath ??
file.dartSymbolMapPath,
commits: args.commits ?? file.commits,
ignoreMissing: args.ignoreMissing ?? file.ignoreMissing,
binDir: args.binDir ?? file.binDir,
Expand Down
104 changes: 104 additions & 0 deletions lib/src/utils/dart_symbol_map.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
import 'package:file/file.dart';

import '../configuration.dart';
import 'flutter_debug_files.dart';

/// If [configuredPath] is provided, validates and returns the absolute path to the Dart symbol map.
/// If not provided, returns null.
///
/// Note: we do not scan the filesystem for this file because the file does not
/// have a special extension so worst case we would have to check every file.
Future<String?> resolveDartSymbolMapPath({
required FileSystem fs,
String? configuredPath,
}) async {
if (configuredPath == null || configuredPath.isEmpty) return null;

final file = fs.file(configuredPath);
if (!await file.exists()) {
throw StateError(
"Dart symbol map file not found at '$configuredPath'. Ensure the path is correct and the file exists.",
);
}

return file.absolute.path;
}

/// Finds Flutter-relevant debug file paths for Android and Apple (iOS/macOS)
/// that should be paired with a Dart symbol map.
Future<Set<String>> findFlutterRelevantDebugFilePaths({
required FileSystem fs,
required Configuration config,
}) async {
final Set<String> foundPaths = <String>{};

Future<void> collectAndroidSymbolsUnder(String rootPath) async {
if (rootPath.isEmpty) return;

final directory = fs.directory(rootPath);
if (await directory.exists()) {
await for (final entity
in directory.list(recursive: true, followLinks: false)) {
if (entity is! File) continue;
final String basename = fs.path.basename(entity.path);
if (basename.startsWith('app') &&
basename.endsWith('.symbols') &&
!basename.contains('darwin')) {
foundPaths.add(fs.file(entity.path).absolute.path);
}
}
return;
}

final file = fs.file(rootPath);
if (await file.exists()) {
final String basename = fs.path.basename(file.path);
if (basename.startsWith('app') &&
basename.endsWith('.symbols') &&
!basename.contains('darwin')) {
foundPaths.add(file.absolute.path);
}
}
}

if (config.symbolsFolder.isNotEmpty) {
await collectAndroidSymbolsUnder(config.symbolsFolder);
}

if (config.buildFilesFolder != config.symbolsFolder) {
await collectAndroidSymbolsUnder(config.buildFilesFolder);
}

Future<void> collectAppleMachOUnder(String rootPath) async {
if (rootPath.isEmpty) return;
final dir = fs.directory(rootPath);
if (!await dir.exists()) return;

await for (final entity in dir.list(recursive: true, followLinks: false)) {
if (entity is! Directory) continue;
final String basename = fs.path.basename(entity.path);
if (basename == 'App.framework.dSYM') {
final String machOPath = fs.path.join(
entity.path,
'Contents',
'Resources',
'DWARF',
'App',
);
final File machOFile = fs.file(machOPath);
if (await machOFile.exists()) {
foundPaths.add(machOFile.absolute.path);
}
}
}
}

await collectAppleMachOUnder(config.buildFilesFolder);

await for (final root in enumerateDebugSearchRoots(fs: fs, config: config)) {
await collectAppleMachOUnder(root);
await collectAndroidSymbolsUnder(root);
}

return foundPaths;
}
Loading
Loading