Detect Flutter SDK location relative to Dart SDK (#3045)

* Detect Flutter SDK location relative to Dart SDK

If the Dart SDK is present inside the Flutter SDK in sub-folder
`bin/cache/dart-sdk/`, then we no-longer require the environment
variable `FLUTTER_ROOT` to be specified. Instead we simply derive
the location of the Flutter SDK from the location of the Dart SDK.

We still allow the environment variable `FLUTTER_ROOT` to override
the otherwise automatically detected Flutter SDK location.

* Check that "version" file is present before deciding that we have found a Flutter SDK
diff --git a/lib/src/sdk/flutter.dart b/lib/src/sdk/flutter.dart
index d74adde..3c8411f 100644
--- a/lib/src/sdk/flutter.dart
+++ b/lib/src/sdk/flutter.dart
@@ -20,25 +20,67 @@
   @override
   Version get firstPubVersion => Version.parse('1.19.0');
 
-  static final bool _isAvailable =
-      Platform.environment.containsKey('FLUTTER_ROOT');
-  static final String _rootDirectory = Platform.environment['FLUTTER_ROOT'];
+  // We only consider the Flutter SDK to present if we find a root directory
+  // and the root directory contains a valid 'version' file.
+  static final bool _isAvailable = _rootDirectory != null && _version != null;
+  static final String _rootDirectory = () {
+    // If FLUTTER_ROOT is specified, then this always points to the Flutter SDK
+    if (Platform.environment.containsKey('FLUTTER_ROOT')) {
+      return Platform.environment['FLUTTER_ROOT'];
+    }
+
+    // We can try to find the Flutter SDK relative to the Dart SDK.
+    // We know that the Dart SDK is always present, this is found relative to
+    // the `dart` executable, for details see: lib/src/sdk/dart.dart
+    //
+    // Once we have the location of the Dart SDK, we can look at its parent
+    // directories, if going 3 levels-up and down `bin/cache/dart-sdk/` is equal
+    // to the Dart SDK root, then it's probably because we are located inside
+    // the Flutter SDK, at: `$FLUTTER_ROOT/bin/cache/dart-sdk`
+    final parts = p.split(sdk.rootDirectory);
+    if (parts.length > 3) {
+      // Go 3-levels up from the Dart SDK root
+      final flutterSdk = p.joinAll(parts.take(parts.length - 3));
+      // If going down 'bin/cache/dart-sdk/' yields the same path as the Dart
+      // SDK has, then it's probably because the Dart SDK is located inside
+      // the Flutter SDK.
+      final dartRootFromFlutterSdk = p.join(
+        flutterSdk,
+        'bin',
+        'cache',
+        'dart-sdk',
+      );
+      if (p.equals(sdk.rootDirectory, dartRootFromFlutterSdk)) {
+        return flutterSdk;
+      }
+    }
+
+    return null;
+  }();
+  static final Version _version = () {
+    if (_rootDirectory == null) return null;
+
+    try {
+      return Version.parse(
+        readTextFile(p.join(_rootDirectory, 'version')).trim(),
+      );
+    } on IOException {
+      return null; // I guess the file doesn't exist
+    } on FormatException {
+      return null; // I guess the file has the wrong format
+    }
+  }();
 
   @override
   String get installMessage =>
-      'Flutter users should run `flutter pub get` instead of `pub get`.';
+      'Flutter users should run `flutter pub get` instead of `dart pub get`.';
 
   @override
   Version get version {
-    if (!_isAvailable) return null;
-
-    _version ??=
-        Version.parse(readTextFile(p.join(_rootDirectory, 'version')).trim());
+    if (!isAvailable) return null;
     return _version;
   }
 
-  Version _version;
-
   @override
   String packagePath(String name) {
     if (!isAvailable) return null;
diff --git a/test/sdk_test.dart b/test/sdk_test.dart
index e1d311b..93ebdac 100644
--- a/test/sdk_test.dart
+++ b/test/sdk_test.dart
@@ -133,7 +133,7 @@
               Because myapp depends on foo any from sdk which doesn't exist (the
                 Flutter SDK is not available), version solving failed.
 
-              Flutter users should run `flutter pub get` instead of `pub
+              Flutter users should run `flutter pub get` instead of `dart pub
                 get`.
             """), exitCode: exit_codes.UNAVAILABLE);
       });
diff --git a/test/version_solver_test.dart b/test/version_solver_test.dart
index 44d0399..41003bb 100644
--- a/test/version_solver_test.dart
+++ b/test/version_solver_test.dart
@@ -1458,7 +1458,7 @@
       await expectResolves(error: equalsIgnoringWhitespace('''
         Because myapp requires the Flutter SDK, version solving failed.
 
-        Flutter users should run `flutter pub get` instead of `pub get`.
+        Flutter users should run `flutter pub get` instead of `dart pub get`.
       '''));
     });
 
@@ -1474,7 +1474,7 @@
         Because myapp depends on foo any which requires the Flutter SDK, version
           solving failed.
 
-        Flutter users should run `flutter pub get` instead of `pub get`.
+        Flutter users should run `flutter pub get` instead of `dart pub get`.
       '''));
     });
 
@@ -1502,7 +1502,7 @@
       await expectResolves(error: equalsIgnoringWhitespace('''
         Because myapp requires the Flutter SDK, version solving failed.
 
-        Flutter users should run `flutter pub get` instead of `pub get`.
+        Flutter users should run `flutter pub get` instead of `dart pub get`.
       '''));
     });
   });