Skip to content

Commit 483e1d9

Browse files
Validate bins on path in doctor (#113106)
1 parent 482466e commit 483e1d9

File tree

2 files changed

+152
-4
lines changed

2 files changed

+152
-4
lines changed

packages/flutter_tools/lib/src/doctor.dart

+38-3
Original file line numberDiff line numberDiff line change
@@ -512,7 +512,10 @@ class FlutterValidator extends DoctorValidator {
512512
versionChannel = version.channel;
513513
frameworkVersion = version.frameworkVersion;
514514

515-
messages.add(_getFlutterVersionMessage(frameworkVersion, versionChannel));
515+
final String flutterRoot = _flutterRoot();
516+
messages.add(_getFlutterVersionMessage(frameworkVersion, versionChannel, flutterRoot));
517+
518+
_validateRequiredBinaries(flutterRoot).forEach(messages.add);
516519
messages.add(_getFlutterUpstreamMessage(version));
517520
if (gitUrl != null) {
518521
messages.add(ValidationMessage(_userMessages.flutterGitUrl(gitUrl)));
@@ -575,8 +578,8 @@ class FlutterValidator extends DoctorValidator {
575578
);
576579
}
577580

578-
ValidationMessage _getFlutterVersionMessage(String frameworkVersion, String versionChannel) {
579-
String flutterVersionMessage = _userMessages.flutterVersion(frameworkVersion, versionChannel, _flutterRoot());
581+
ValidationMessage _getFlutterVersionMessage(String frameworkVersion, String versionChannel, String flutterRoot) {
582+
String flutterVersionMessage = _userMessages.flutterVersion(frameworkVersion, versionChannel, flutterRoot);
580583

581584
// The tool sets the channel as "unknown", if the current branch is on a
582585
// "detached HEAD" state or doesn't have an upstream, and sets the
@@ -594,6 +597,38 @@ class FlutterValidator extends DoctorValidator {
594597
return ValidationMessage.hint(flutterVersionMessage);
595598
}
596599

600+
List<ValidationMessage> _validateRequiredBinaries(String flutterRoot) {
601+
final ValidationMessage? flutterWarning = _validateSdkBinary('flutter', flutterRoot);
602+
final ValidationMessage? dartWarning = _validateSdkBinary('dart', flutterRoot);
603+
return <ValidationMessage>[
604+
if (flutterWarning != null) flutterWarning,
605+
if (dartWarning != null) dartWarning,
606+
];
607+
}
608+
609+
/// Return a warning if the provided [binary] on the user's path does not
610+
/// resolve within the Flutter SDK.
611+
ValidationMessage? _validateSdkBinary(String binary, String flutterRoot) {
612+
final String flutterBinDir = _fileSystem.path.join(flutterRoot, 'bin');
613+
614+
final File? flutterBin = _operatingSystemUtils.which(binary);
615+
if (flutterBin == null) {
616+
return ValidationMessage.hint(
617+
'The $binary binary is not on your path. Consider adding '
618+
'$flutterBinDir to your path.',
619+
);
620+
}
621+
final String resolvedFlutterPath = flutterBin.resolveSymbolicLinksSync();
622+
if (!resolvedFlutterPath.contains(flutterRoot)) {
623+
final String hint = 'Warning: `$binary` on your path resolves to '
624+
'$resolvedFlutterPath, which is not inside your current Flutter '
625+
'SDK checkout at $flutterRoot. Consider adding $flutterBinDir to '
626+
'the front of your path.';
627+
return ValidationMessage.hint(hint);
628+
}
629+
return null;
630+
}
631+
597632
ValidationMessage _getFlutterUpstreamMessage(FlutterVersion version) {
598633
final String? repositoryUrl = version.repositoryUrl;
599634
final VersionCheckError? upstreamValidationError = VersionUpstreamValidator(version: version, platform: _platform).run();

packages/flutter_tools/test/general.shard/flutter_validator_test.dart

+114-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Use of this source code is governed by a BSD-style license that can be
33
// found in the LICENSE file.
44

5+
import 'package:file/file.dart';
56
import 'package:file/memory.dart';
67
import 'package:flutter_tools/src/artifacts.dart';
78
import 'package:flutter_tools/src/base/os.dart';
@@ -384,10 +385,122 @@ void main() {
384385
),
385386
));
386387
});
388+
389+
testWithoutContext('detects no flutter and dart on path', () async {
390+
final FlutterValidator flutterValidator = FlutterValidator(
391+
platform: FakePlatform(localeName: 'en_US.UTF-8'),
392+
flutterVersion: () => FakeFlutterVersion(
393+
frameworkVersion: '1.0.0',
394+
channel: 'beta'
395+
),
396+
devToolsVersion: () => '2.8.0',
397+
userMessages: UserMessages(),
398+
artifacts: Artifacts.test(),
399+
fileSystem: MemoryFileSystem.test(),
400+
processManager: FakeProcessManager.any(),
401+
operatingSystemUtils: FakeOperatingSystemUtils(
402+
name: 'Linux',
403+
whichLookup: const <String, File>{},
404+
),
405+
flutterRoot: () => 'sdk/flutter',
406+
);
407+
408+
expect(await flutterValidator.validate(), _matchDoctorValidation(
409+
validationType: ValidationType.partial,
410+
statusInfo: 'Channel beta, 1.0.0, on Linux, locale en_US.UTF-8',
411+
messages: contains(const ValidationMessage.hint(
412+
'The flutter binary is not on your path. Consider adding sdk/flutter/bin to your path.',
413+
)),
414+
));
415+
});
416+
417+
testWithoutContext('detects flutter and dart from outside flutter sdk', () async {
418+
final FileSystem fs = MemoryFileSystem.test();
419+
final FlutterValidator flutterValidator = FlutterValidator(
420+
platform: FakePlatform(localeName: 'en_US.UTF-8'),
421+
flutterVersion: () => FakeFlutterVersion(
422+
frameworkVersion: '1.0.0',
423+
channel: 'beta'
424+
),
425+
devToolsVersion: () => '2.8.0',
426+
userMessages: UserMessages(),
427+
artifacts: Artifacts.test(),
428+
fileSystem: fs,
429+
processManager: FakeProcessManager.any(),
430+
operatingSystemUtils: FakeOperatingSystemUtils(
431+
name: 'Linux',
432+
whichLookup: <String, File>{
433+
'flutter': fs.file('/usr/bin/flutter')..createSync(recursive: true),
434+
'dart': fs.file('/usr/bin/dart')..createSync(recursive: true),
435+
},
436+
),
437+
flutterRoot: () => 'sdk/flutter',
438+
);
439+
440+
expect(await flutterValidator.validate(), _matchDoctorValidation(
441+
validationType: ValidationType.partial,
442+
statusInfo: 'Channel beta, 1.0.0, on Linux, locale en_US.UTF-8',
443+
messages: contains(const ValidationMessage.hint(
444+
'Warning: `flutter` on your path resolves to /usr/bin/flutter, which '
445+
'is not inside your current Flutter SDK checkout at sdk/flutter. '
446+
'Consider adding sdk/flutter/bin to the front of your path.',
447+
)),
448+
));
449+
});
450+
451+
testWithoutContext('no warnings if flutter & dart binaries are inside the Flutter SDK', () async {
452+
final FileSystem fs = MemoryFileSystem.test();
453+
final FlutterValidator flutterValidator = FlutterValidator(
454+
platform: FakePlatform(localeName: 'en_US.UTF-8'),
455+
flutterVersion: () => FakeFlutterVersion(
456+
frameworkVersion: '1.0.0',
457+
channel: 'beta'
458+
),
459+
devToolsVersion: () => '2.8.0',
460+
userMessages: UserMessages(),
461+
artifacts: Artifacts.test(),
462+
fileSystem: fs,
463+
processManager: FakeProcessManager.any(),
464+
operatingSystemUtils: FakeOperatingSystemUtils(
465+
name: 'Linux',
466+
whichLookup: <String, File>{
467+
'flutter': fs.file('/sdk/flutter/bin/flutter')..createSync(recursive: true),
468+
'dart': fs.file('/sdk/flutter/bin/dart')..createSync(recursive: true),
469+
},
470+
),
471+
flutterRoot: () => '/sdk/flutter',
472+
);
473+
474+
expect(await flutterValidator.validate(), _matchDoctorValidation(
475+
validationType: ValidationType.installed,
476+
statusInfo: 'Channel beta, 1.0.0, on Linux, locale en_US.UTF-8',
477+
messages: isNot(contains(isA<ValidationMessage>().having(
478+
(ValidationMessage message) => message.message,
479+
'message',
480+
contains('Consider adding /sdk/flutter/bin to the front of your path'),
481+
))),
482+
));
483+
});
387484
}
388485

389486
class FakeOperatingSystemUtils extends Fake implements OperatingSystemUtils {
390-
FakeOperatingSystemUtils({required this.name});
487+
FakeOperatingSystemUtils({
488+
required this.name,
489+
this.whichLookup,
490+
FileSystem? fs,
491+
}) {
492+
fs ??= MemoryFileSystem.test();
493+
whichLookup ??= <String, File>{
494+
'flutter': fs.file('/sdk/flutter/bin/flutter')..createSync(recursive: true),
495+
'dart': fs.file('/sdk/flutter/bin/dart')..createSync(recursive: true),
496+
};
497+
}
498+
499+
/// A map of [File]s that calls to [which] will return.
500+
Map<String, File>? whichLookup;
501+
502+
@override
503+
File? which(String execName) => whichLookup![execName];
391504

392505
@override
393506
final String name;

0 commit comments

Comments
 (0)