Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
2 changes: 1 addition & 1 deletion packages/devtools_shared/lib/src/server/file_system.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down
21 changes: 15 additions & 6 deletions tool/lib/model.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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.
}
Comment thread
johnpryan marked this conversation as resolved.

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"');
Expand All @@ -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');
Expand Down
61 changes: 61 additions & 0 deletions tool/test/model_test.dart
Original file line number Diff line number Diff line change
@@ -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<Exception>()),
);
});
});
}
Loading