diff --git a/packages/devtools_app/lib/src/screens/performance/performance_controller.dart b/packages/devtools_app/lib/src/screens/performance/performance_controller.dart index a41441fcf36..b9e158009c5 100644 --- a/packages/devtools_app/lib/src/screens/performance/performance_controller.dart +++ b/packages/devtools_app/lib/src/screens/performance/performance_controller.dart @@ -48,13 +48,10 @@ class PerformanceController extends DevToolsScreenController @override final screenId = ScreenMetaData.performance.id; - // ignore: dispose-class-fields, false positive. See `applyToFeatureControllers` in the dispose() method. late final FlutterFramesController flutterFramesController; - // ignore: dispose-class-fields, false positive. See `applyToFeatureControllers` in the dispose() method. late final TimelineEventsController timelineEventsController; - // ignore: dispose-class-fields, false positive. See `applyToFeatureControllers` in the dispose() method. late final RebuildStatsController rebuildStatsController; // ignore: dispose-class-fields, false positive. See `applyToFeatureControllers` in the dispose() method. diff --git a/packages/devtools_shared/lib/src/server/file_system.dart b/packages/devtools_shared/lib/src/server/file_system.dart index 450790b47fd..63918084354 100644 --- a/packages/devtools_shared/lib/src/server/file_system.dart +++ b/packages/devtools_shared/lib/src/server/file_system.dart @@ -12,7 +12,7 @@ import 'package:path/path.dart' as path; import 'devtools_store.dart'; /// The real, local file system, which can be avoided in tests. -const FileSystem fileSystem = LocalFileSystem(); +const fileSystem = LocalFileSystem(); extension FileSystemExtension on FileSystem { static String get _userHomeDir { diff --git a/tool/lib/model.dart b/tool/lib/model.dart index 2032b4c5ec7..12df45554aa 100644 --- a/tool/lib/model.dart +++ b/tool/lib/model.dart @@ -242,8 +242,20 @@ class FlutterSdk { } static FlutterSdk findFromPath(String sdkPath) { - if (path.basename(path.dirname(sdkPath)) == 'bin') { - return FlutterSdk._(path.dirname(path.dirname(sdkPath))); + var resolvedPath = sdkPath; + try { + resolvedPath = File(sdkPath).resolveSymbolicLinksSync(); + } catch (_) { + // Fallback to the unresolved path if resolution fails. + } + + final resolvedPathParent = path.dirname(resolvedPath); + if (path.basename(resolvedPathParent) == 'bin') { + return FlutterSdk._(path.dirname(resolvedPathParent)); + } else if (path.basename(resolvedPath) == 'bin') { + return FlutterSdk._(resolvedPathParent); + } else if (Directory(path.join(resolvedPath, 'bin')).existsSync()) { + return FlutterSdk._(resolvedPath); } throw Exception('Unable to locate the Flutter SDK at "$sdkPath"'); @@ -258,10 +270,7 @@ class FlutterSdk { final result = Process.runSync(whichCommand, ['flutter']); if (result.exitCode == 0) { final sdkPath = result.stdout.toString().split('\n').first.trim(); - // 'flutter/bin' - if (path.basename(path.dirname(sdkPath)) == 'bin') { - return FlutterSdk._(path.dirname(path.dirname(sdkPath))); - } + return findFromPath(sdkPath); } throw Exception('Unable to locate the Flutter SDK on PATH'); diff --git a/tool/test/model_test.dart b/tool/test/model_test.dart new file mode 100644 index 00000000000..5279086b286 --- /dev/null +++ b/tool/test/model_test.dart @@ -0,0 +1,61 @@ +// Copyright 2026 The Flutter Authors +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file or at https://developers.google.com/open-source/licenses/bsd. + +import 'dart:io'; + +import 'package:devtools_tool/model.dart'; +import 'package:path/path.dart' as path; +import 'package:test/test.dart'; + +void main() { + group('FlutterSdk.findFromPath', () { + late Directory tempDir; + + setUp(() { + tempDir = Directory.systemTemp.createTempSync('flutter_sdk_test_'); + }); + + tearDown(() { + tempDir.deleteSync(recursive: true); + }); + + test('finds SDK root from bin/flutter executable path', () { + final binDir = Directory(path.join(tempDir.path, 'bin'))..createSync(); + final flutterExe = File(path.join(binDir.path, 'flutter'))..createSync(); + + final sdk = FlutterSdk.findFromPath(flutterExe.path); + expect(sdk.sdkPath, equals(tempDir.resolveSymbolicLinksSync())); + }); + + test('resolves symbolic links to find actual SDK root', () { + final sdkDir = Directory(path.join(tempDir.path, 'real_sdk')) + ..createSync(); + final binDir = Directory(path.join(sdkDir.path, 'bin'))..createSync(); + final flutterExe = File(path.join(binDir.path, 'flutter'))..createSync(); + + final linkDir = Directory(path.join(tempDir.path, 'symlink_bin')) + ..createSync(); + final symlink = Link(path.join(linkDir.path, 'flutter')); + symlink.createSync(flutterExe.path); + + final sdk = FlutterSdk.findFromPath(symlink.path); + expect(sdk.sdkPath, equals(sdkDir.resolveSymbolicLinksSync())); + }); + + test('finds SDK root when given SDK directory path directly', () { + final binDir = Directory(path.join(tempDir.path, 'bin'))..createSync(); + File(path.join(binDir.path, 'flutter')).createSync(); + + final sdk = FlutterSdk.findFromPath(tempDir.path); + expect(sdk.sdkPath, equals(tempDir.resolveSymbolicLinksSync())); + }); + + test('throws Exception when unable to locate Flutter SDK', () { + expect( + () => FlutterSdk.findFromPath('/non/existent/path/to/flutter'), + throwsA(isA()), + ); + }); + }); +}