Skip to content
Closed
Show file tree
Hide file tree
Changes from all 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
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
62 changes: 9 additions & 53 deletions lib/sentry_dart_plugin.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ 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/injector.dart';
import 'src/utils/log.dart';

Expand Down Expand Up @@ -86,7 +87,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 +99,13 @@ 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';

// 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';
final _ = await findFlutterRelevantDebugFilePaths(
fs: fs,
config: _configuration,
);
// TODO(buenaflor): in the follow up PR use these files with the dart symbol mapping file

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

Future<Set<String>> _enumerateSymbolFiles() async {
Expand Down
155 changes: 155 additions & 0 deletions lib/src/utils/flutter_debug_files.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import 'package:file/file.dart';

import '../configuration.dart';

/// Finds Flutter-relevant debug file paths for Android and Apple (iOS/macOS).
/// TODO(buenaflor): in the follow-up PR this should be coupled together with the 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);
}
}
}

// First, scan the configured symbols folder (if any)
if (config.symbolsFolder.isNotEmpty) {
await collectAndroidSymbolsUnder(config.symbolsFolder);
}

// Backward compatibility: also scan build folder if different
if (config.buildFilesFolder != config.symbolsFolder) {
await collectAndroidSymbolsUnder(config.buildFilesFolder);
}

// Then, scan all current search roots used by the plugin
await for (final root in enumerateDebugSearchRoots(fs: fs, config: config)) {
await collectAndroidSymbolsUnder(root);
}

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);
}
}
}
}

// Search under the build directory directly to catch common iOS layouts
await collectAppleMachOUnder(config.buildFilesFolder);

// Search all known roots (includes Fastlane ios/build)
await for (final root in enumerateDebugSearchRoots(fs: fs, config: config)) {
await collectAppleMachOUnder(root);
}

return foundPaths;
}

/// Enumerates the search roots used to discover native debug files, matching
/// the existing behavior used by the plugin when uploading debug files.
///
/// This preserves current directories and files probed for:
/// - Android (apk, appbundle)
/// - Windows
/// - Linux
/// - macOS (app and framework)
/// - iOS (Runner.app, Release-*-iphoneos folders, archive, framework)
/// - iOS in Fastlane (ios/build)
Stream<String> enumerateDebugSearchRoots({
required FileSystem fs,
required Configuration config,
}) async* {
final String buildDir = config.buildFilesFolder;
final String 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: Consider removing once Windows symbols are collected automatically.
// Related to https://github.com/getsentry/sentry-dart-plugin/issues/173
yield 'windows/flutter/ephemeral/flutter_windows.dll.pdb';

// 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';
final iosDir = fs.directory('$buildDir/ios');
if (await iosDir.exists()) {
final regexp = RegExp(r'^Release(-.*)?-iphoneos$');
yield* iosDir
.list()
.where((entity) => regexp.hasMatch(fs.path.basename(entity.path)))
.map((entity) => entity.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';
}
}
Loading
Loading