add support for an sdk_packages.yaml file in the Dart SDK (#4151)

Part of https://github.com/dart-lang/pub/issues/3980
diff --git a/lib/src/pubspec_parse.dart b/lib/src/pubspec_parse.dart
index 1c63484..48a7f3d 100644
--- a/lib/src/pubspec_parse.dart
+++ b/lib/src/pubspec_parse.dart
@@ -7,7 +7,7 @@
 import 'package:yaml/yaml.dart';
 
 import 'exceptions.dart';
-import 'utils.dart' show identifierRegExp, reservedWords;
+import 'utils.dart' show identifierRegExp, ExpectField;
 
 /// A regular expression matching allowed package names.
 ///
@@ -36,34 +36,7 @@
         _version = version;
 
   /// The package's name.
-  String get name => _name ??= _lookupName();
-
-  String _lookupName() {
-    final name = fields['name'];
-    if (name == null) {
-      throw SourceSpanApplicationException(
-        'Missing the required "name" field.',
-        fields.span,
-      );
-    } else if (name is! String) {
-      throw SourceSpanApplicationException(
-        '"name" field must be a string.',
-        fields.nodes['name']?.span,
-      );
-    } else if (!packageNameRegExp.hasMatch(name)) {
-      throw SourceSpanApplicationException(
-        '"name" field must be a valid Dart identifier.',
-        fields.nodes['name']?.span,
-      );
-    } else if (reservedWords.contains(name.toLowerCase())) {
-      throw SourceSpanApplicationException(
-        '"name" field may not be a Dart reserved word.',
-        fields.nodes['name']?.span,
-      );
-    }
-
-    return name;
-  }
+  String get name => _name ??= fields.expectPackageNameField();
 
   String? _name;
 
diff --git a/lib/src/sdk.dart b/lib/src/sdk.dart
index bc1c6c9..848e71d 100644
--- a/lib/src/sdk.dart
+++ b/lib/src/sdk.dart
@@ -37,6 +37,10 @@
   /// be `null`, indicating that no such message should be printed.
   String? get installMessage;
 
+  /// Whether or not non-SDK dependencies are allowed in the regular
+  /// dependencies section for packages vendored by this SDK.
+  bool get allowsNonSdkDepsInSdkPackages;
+
   /// Returns the path to the package [name] within this SDK.
   ///
   /// Returns `null` if the SDK isn't available or if it doesn't contain a
diff --git a/lib/src/sdk/dart.dart b/lib/src/sdk/dart.dart
index 19af627..f98477b 100644
--- a/lib/src/sdk/dart.dart
+++ b/lib/src/sdk/dart.dart
@@ -6,9 +6,11 @@
 
 import 'package:path/path.dart' as p;
 import 'package:pub_semver/pub_semver.dart';
+import 'package:yaml/yaml.dart';
 
 import '../io.dart';
 import '../sdk.dart';
+import 'sdk_package_config.dart';
 
 /// The Dart SDK.
 ///
@@ -20,8 +22,15 @@
   bool get isAvailable => true;
   @override
   String? get installMessage => null;
+  @override
+  bool get allowsNonSdkDepsInSdkPackages => false;
 
   static final String _rootDirectory = () {
+    // If DART_ROOT is specified, then this always points to the Dart SDK
+    if (Platform.environment['DART_ROOT'] case var root?) {
+      return root;
+    }
+
     if (runningFromDartRepo) return p.join(dartRepoRoot, 'sdk');
 
     // The Dart executable is in "/path/to/sdk/bin/dart", so two levels up is
@@ -31,6 +40,24 @@
     return aboveExecutable;
   }();
 
+  /// The loaded `sdk_packages.yaml` file if present.
+  static final SdkPackageConfig? _sdkPackages = () {
+    var path = p.join(_rootDirectory, 'sdk_packages.yaml');
+    if (!fileExists(path)) return null;
+    final text = readTextFile(path);
+    final yaml = loadYaml(text) as YamlMap;
+    var config = SdkPackageConfig.fromYaml(yaml);
+    if (config.sdk != 'dart') {
+      throw FormatException(
+        'Expected a configuration for the `dart` sdk but got one for '
+        '`${config.sdk}`.',
+        text,
+        (yaml.nodes['sdk']!).span.start.offset,
+      );
+    }
+    return config;
+  }();
+
   @override
   final Version version = () {
     // Some of the pub integration tests require an SDK version number, but the
@@ -50,5 +77,16 @@
   String get rootDirectory => _rootDirectory;
 
   @override
-  String? packagePath(String name) => null;
+  String? packagePath(String name) {
+    if (!isAvailable) return null;
+    var sdkPackages = _sdkPackages;
+    if (sdkPackages == null) return null;
+
+    var package = sdkPackages.packages[name];
+    if (package == null) return null;
+    var packagePath = p.joinAll([_rootDirectory, ...package.path.split('/')]);
+    if (dirExists(packagePath)) return packagePath;
+
+    return null;
+  }
 }
diff --git a/lib/src/sdk/flutter.dart b/lib/src/sdk/flutter.dart
index 8243360..57ed748 100644
--- a/lib/src/sdk/flutter.dart
+++ b/lib/src/sdk/flutter.dart
@@ -19,6 +19,8 @@
 
   @override
   String get name => 'Flutter';
+  @override
+  bool get allowsNonSdkDepsInSdkPackages => true;
 
   // We only consider the Flutter SDK to present if we find a root directory
   // and the root directory contains a valid 'version' file.
diff --git a/lib/src/sdk/fuchsia.dart b/lib/src/sdk/fuchsia.dart
index e0f551b..cdd41bd 100644
--- a/lib/src/sdk/fuchsia.dart
+++ b/lib/src/sdk/fuchsia.dart
@@ -15,6 +15,8 @@
   String get name => 'Fuchsia';
   @override
   bool get isAvailable => _isAvailable;
+  @override
+  bool get allowsNonSdkDepsInSdkPackages => true;
 
   static final bool _isAvailable = _rootDirectory != null;
   static final String? _rootDirectory =
diff --git a/lib/src/sdk/sdk_package_config.dart b/lib/src/sdk/sdk_package_config.dart
new file mode 100644
index 0000000..fe9b300
--- /dev/null
+++ b/lib/src/sdk/sdk_package_config.dart
@@ -0,0 +1,101 @@
+// Copyright (c) 2024, the Dart project authors.  Please see the AUTHORS file
+// for details. All rights reserved. Use of this source code is governed by a
+// BSD-style license that can be found in the LICENSE file.
+
+import 'package:yaml/yaml.dart';
+
+import '../utils.dart' show ExpectField, ExpectEntries;
+
+/// The top level structure of an `sdk_packages.yaml` file.
+///
+/// See https://github.com/dart-lang/pub/issues/3980 for discussion of the
+/// feature and format.
+///
+/// Version 1 of the format is as follows:
+///
+/// ```yaml
+/// # Required, the version of the format used in this file. Not all versions
+/// # will be supported forever, but some number of previous ones will be.
+/// version: 1
+///
+/// # The SDK this configuration file is targeting. Used for validation, to
+/// # ensure we are parsing a file intended for the SDK we are configuring.
+/// sdk: dart
+///
+/// # A list of package descriptors, for each package vendored by this SDK.
+/// packages:
+/// - name: my_sdk_package
+///   # A path relative to the root of the installed SDK, in URL form (with `/`
+///   # path separators).
+///   path: path/to/my_sdk_package
+/// ```
+class SdkPackageConfig {
+  /// The name of the SDK this configuration is for.
+  ///
+  /// SDKs should validate the config is for them on load, and this is the only
+  /// real use for this field.
+  final String sdk;
+
+  /// All the packages vendored by this SDK. Note that the format in the file is
+  /// not a map, but a list. When parsing the file we convert it to map for easy
+  /// lookups.
+  final Map<String, SdkPackage> packages;
+
+  /// The version of the format.
+  final int version;
+
+  SdkPackageConfig(this.sdk, this.packages, this.version);
+
+  factory SdkPackageConfig.fromYaml(YamlMap yaml) {
+    final version = yaml.expectField<int>('version');
+    if (version != 1) {
+      throw UnsupportedError('This SDK only supports version 1 of the '
+          'sdk_packages.yaml format, but got version $version');
+    }
+    final packages = <String, SdkPackage>{};
+    final packageDescriptions =
+        yaml.expectField<YamlList>('packages').expectElements<YamlMap>();
+    for (var description in packageDescriptions) {
+      final package = SdkPackage.fromYaml(description);
+      packages[package.name] = package;
+    }
+
+    return SdkPackageConfig(
+      yaml.expectField<String>('sdk'),
+      packages,
+      version,
+    );
+  }
+
+  Map<String, Object?> toMap() => {
+        'sdk': sdk,
+        'packages': [
+          for (var package in packages.values) package.toMap(),
+        ],
+        'version': version,
+      };
+}
+
+/// The structure for each `packages` entry in an `sdk_packages.yaml` file.
+class SdkPackage {
+  /// The name of the package.
+  final String name;
+
+  /// The path to the root of this package relative to the root of the installed
+  /// SDK.
+  ///
+  /// This path should be in URL format (with forward slashes), and always
+  /// relative.
+  final String path;
+
+  SdkPackage(this.name, this.path);
+
+  SdkPackage.fromYaml(YamlMap yaml)
+      : name = yaml.expectPackageNameField(),
+        path = yaml.expectField<String>('path');
+
+  Map<String, Object?> toMap() => {
+        'name': name,
+        'path': path,
+      };
+}
diff --git a/lib/src/source/sdk.dart b/lib/src/source/sdk.dart
index 878d6d5..24cb658 100644
--- a/lib/src/source/sdk.dart
+++ b/lib/src/source/sdk.dart
@@ -89,12 +89,33 @@
   ///
   /// Throws a [PackageNotFoundException] if [ref]'s SDK is unavailable or
   /// doesn't contain the package.
-  Pubspec _loadPubspec(PackageRef ref, SystemCache cache) => Pubspec.load(
-        _verifiedPackagePath(ref),
-        cache.sources,
-        expectedName: ref.name,
-        containingDescription: ref.description,
-      );
+  Pubspec _loadPubspec(PackageRef ref, SystemCache cache) {
+    var pubspec = Pubspec.load(
+      _verifiedPackagePath(ref),
+      cache.sources,
+      expectedName: ref.name,
+      containingDescription: ref.description,
+    );
+
+    /// Validate that there are no non-sdk dependencies if the SDK does not
+    /// allow them.
+    if (ref.description case SdkDescription description) {
+      if (sdks[description.sdk]
+          case Sdk(allowsNonSdkDepsInSdkPackages: false)) {
+        for (var dep in pubspec.dependencies.entries) {
+          if (dep.value.source is! SdkSource) {
+            throw UnsupportedError(
+              'Only SDK packages are allowed as regular dependencies for '
+              'packages vendored by the ${sdk.identifier} SDK, but the '
+              '`${ref.name}` package has a ${dep.value.source.name} dependency '
+              'on `${dep.key}`.',
+            );
+          }
+        }
+      }
+    }
+    return pubspec;
+  }
 
   /// Returns the path for the given [ref].
   ///
diff --git a/lib/src/utils.dart b/lib/src/utils.dart
index 754cc87..cb971ff 100644
--- a/lib/src/utils.dart
+++ b/lib/src/utils.dart
@@ -16,10 +16,12 @@
 import 'package:crypto/crypto.dart' as crypto;
 import 'package:pub_semver/pub_semver.dart';
 import 'package:stack_trace/stack_trace.dart';
+import 'package:yaml/yaml.dart';
 
 import 'exceptions.dart';
 import 'io.dart';
 import 'log.dart' as log;
+import 'pubspec_parse.dart';
 
 /// A regular expression matching a Dart identifier.
 ///
@@ -777,3 +779,60 @@
 String sanitizeForTerminal(String input) => String.fromCharCodes(
       input.runes.map((r) => 32 <= r && r <= 127 ? r : 32).take(1024),
     );
+
+extension ExpectField on YamlMap {
+  /// Looks up the [key] in this map, and validates that it is of type [T],
+  /// returning it if so.
+  ///
+  /// Throws a [SourceSpanApplicationException] if not present and [T] is not
+  /// nullable, or if the value is not of type [T].
+  T expectField<T extends Object?>(String key) {
+    final value = this[key];
+    if (value is T) return value;
+    if (value == null) {
+      throw SourceSpanApplicationException(
+        'Missing the required "$key" field.',
+        span,
+      );
+    } else {
+      throw SourceSpanApplicationException(
+        '"$key" field must be a $T.',
+        nodes[key]?.span,
+      );
+    }
+  }
+
+  String expectPackageNameField() {
+    final name = expectField<String>('name');
+    if (!packageNameRegExp.hasMatch(name)) {
+      throw SourceSpanApplicationException(
+        '"name" field must be a valid Dart identifier.',
+        nodes['name']?.span,
+      );
+    } else if (reservedWords.contains(name.toLowerCase())) {
+      throw SourceSpanApplicationException(
+        '"name" field may not be a Dart reserved word.',
+        nodes['name']?.span,
+      );
+    }
+    return name;
+  }
+}
+
+extension ExpectEntries on YamlList {
+  /// Expects each entry in [this] to have a value of type [T],
+  /// and returns a `List<T>`.
+  ///
+  /// Throws a [SourceSpanApplicationException] for the first entry that does
+  /// not have a value of type [T].
+  List<T> expectElements<T extends Object?>() => [
+        for (var node in nodes)
+          if (node.value case T value)
+            value
+          else
+            throw SourceSpanApplicationException(
+              'Elements must be of type $T.',
+              node.span,
+            ),
+      ];
+}
diff --git a/test/descriptor.dart b/test/descriptor.dart
index 678626a..b0e68c2 100644
--- a/test/descriptor.dart
+++ b/test/descriptor.dart
@@ -11,6 +11,7 @@
 import 'package:pub/src/language_version.dart';
 import 'package:pub/src/oauth2.dart';
 import 'package:pub/src/package_config.dart';
+import 'package:pub/src/sdk/sdk_package_config.dart';
 import 'package:test_descriptor/test_descriptor.dart';
 
 import 'descriptor/git.dart';
@@ -357,13 +358,6 @@
   String? languageVersion,
   PackageServer? server,
 }) {
-  if (version != null && path != null) {
-    throw ArgumentError.value(
-      path,
-      'path',
-      'Only one of "version" and "path" can be provided',
-    );
-  }
   if (version == null && path == null) {
     throw ArgumentError.value(
       version,
@@ -372,7 +366,7 @@
     );
   }
   Uri rootUri;
-  if (version != null) {
+  if (path == null && version != null) {
     rootUri = p.toUri((server ?? globalServer).pathInCache(name, version));
   } else {
     rootUri = p.toUri(p.join('..', path));
@@ -394,3 +388,8 @@
     ),
   ]);
 }
+
+/// Describes a file named `sdk_packages.yaml` at the root of the current SDK.
+FileDescriptor sdkPackagesConfig(SdkPackageConfig sdkPackageConfig) {
+  return YamlDescriptor('sdk_packages.yaml', yaml(sdkPackageConfig.toMap()));
+}
diff --git a/test/sdk_test.dart b/test/sdk_test.dart
index 00f5313..39b3fa2 100644
--- a/test/sdk_test.dart
+++ b/test/sdk_test.dart
@@ -5,6 +5,7 @@
 import 'package:path/path.dart' as p;
 import 'package:pub/src/exit_codes.dart' as exit_codes;
 import 'package:pub/src/io.dart';
+import 'package:pub/src/sdk/sdk_package_config.dart';
 import 'package:test/test.dart';
 
 import 'descriptor.dart' as d;
@@ -12,155 +13,29 @@
 
 void main() {
   forBothPubGetAndUpgrade((command) {
-    setUp(() async {
-      final server = await servePackages();
-      server.serve('bar', '1.0.0');
+    group('flutter', () {
+      setUp(() async {
+        final server = await servePackages();
+        server.serve('bar', '1.0.0');
 
-      await d.dir('flutter', [
-        d.dir('packages', [
-          d.dir('foo', [
-            d.libDir('foo', 'foo 0.0.1'),
-            d.libPubspec('foo', '0.0.1', deps: {'bar': 'any'}),
+        await d.dir('flutter', [
+          d.dir('packages', [
+            d.dir('foo', [
+              d.libDir('foo', 'foo 0.0.1'),
+              d.libPubspec('foo', '0.0.1', deps: {'bar': 'any'}),
+            ]),
           ]),
-        ]),
-        d.dir('bin/cache/pkg', [
-          d.dir(
-            'baz',
-            [d.libDir('baz', 'foo 0.0.1'), d.libPubspec('baz', '0.0.1')],
-          ),
-        ]),
-        d.flutterVersion('1.2.3'),
-      ]).create();
-    });
-
-    test("gets an SDK dependency's dependencies", () async {
-      await d.appDir(
-        dependencies: {
-          'foo': {'sdk': 'flutter'},
-        },
-      ).create();
-      await pubCommand(
-        command,
-        environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
-      );
-      await d.appPackageConfigFile(
-        [
-          d.packageConfigEntry(
-            name: 'foo',
-            path: p.join(d.sandbox, 'flutter', 'packages', 'foo'),
-          ),
-          d.packageConfigEntry(name: 'bar', version: '1.0.0'),
-        ],
-        flutterRoot: p.join(d.sandbox, 'flutter'),
-        flutterVersion: '1.2.3',
-      ).validate();
-    });
-
-    test('gets an SDK dependency from bin/cache/pkg', () async {
-      await d.appDir(
-        dependencies: {
-          'baz': {'sdk': 'flutter'},
-        },
-      ).create();
-      await pubCommand(
-        command,
-        environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
-      );
-
-      await d.appPackageConfigFile(
-        [
-          d.packageConfigEntry(
-            name: 'baz',
-            path: p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz'),
-          ),
-        ],
-        flutterRoot: p.join(d.sandbox, 'flutter'),
-        flutterVersion: '1.2.3',
-      ).validate();
-    });
-
-    test('unlocks an SDK dependency when the version changes', () async {
-      await d.appDir(
-        dependencies: {
-          'foo': {'sdk': 'flutter'},
-        },
-      ).create();
-      await pubCommand(
-        command,
-        environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
-      );
-
-      await d
-          .file(
-            '$appPath/pubspec.lock',
-            allOf([contains('0.0.1'), isNot(contains('0.0.2'))]),
-          )
-          .validate();
-
-      await d
-          .dir('flutter/packages/foo', [d.libPubspec('foo', '0.0.2')]).create();
-      await pubCommand(
-        command,
-        environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
-      );
-
-      await d
-          .file(
-            '$appPath/pubspec.lock',
-            allOf([isNot(contains('0.0.1')), contains('0.0.2')]),
-          )
-          .validate();
-    });
-
-    // Regression test for #1883
-    test(
-        "doesn't fail if the Flutter SDK's version file doesn't exist when "
-        'nothing depends on Flutter', () async {
-      await d.appDir().create();
-      deleteEntry(
-        p.join(d.sandbox, 'flutter', 'bin', 'cache', 'flutterVersion'),
-      );
-      await pubCommand(
-        command,
-        environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
-      );
-      await d.appPackageConfigFile(
-        [],
-        flutterRoot: p.join(d.sandbox, 'flutter'),
-        flutterVersion: '1.2.3',
-      ).validate();
-    });
-
-    group('fails if', () {
-      test("the version constraint doesn't match", () async {
-        await d.appDir(
-          dependencies: {
-            'foo': {'sdk': 'flutter', 'version': '^1.0.0'},
-          },
-        ).create();
-        await pubCommand(
-          command,
-          environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
-          error: contains('''
-Because myapp depends on foo ^1.0.0 from sdk which doesn't match any versions, version solving failed.'''),
-        );
+          d.dir('bin/cache/pkg', [
+            d.dir(
+              'baz',
+              [d.libDir('baz', 'foo 0.0.1'), d.libPubspec('baz', '0.0.1')],
+            ),
+          ]),
+          d.flutterVersion('1.2.3'),
+        ]).create();
       });
 
-      test('the SDK is unknown', () async {
-        await d.appDir(
-          dependencies: {
-            'foo': {'sdk': 'unknown'},
-          },
-        ).create();
-        await pubCommand(
-          command,
-          error: equalsIgnoringWhitespace('''
-Because myapp depends on foo from sdk which doesn't exist (unknown SDK "unknown"), version solving failed.'''),
-          exitCode: exit_codes.UNAVAILABLE,
-        );
-      });
-
-      test('the SDK is unavailable', () async {
+      test("gets an SDK dependency's dependencies", () async {
         await d.appDir(
           dependencies: {
             'foo': {'sdk': 'flutter'},
@@ -168,71 +43,343 @@
         ).create();
         await pubCommand(
           command,
-          error: equalsIgnoringWhitespace("""
-              Because myapp depends on foo from sdk which doesn't exist (the
-                Flutter SDK is not available), version solving failed.
-
-              Flutter users should use `flutter pub` instead of `dart pub`.
-            """),
-          exitCode: exit_codes.UNAVAILABLE,
+          environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
         );
+        await d.appPackageConfigFile(
+          [
+            d.packageConfigEntry(
+              name: 'foo',
+              path: p.join(d.sandbox, 'flutter', 'packages', 'foo'),
+            ),
+            d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+          ],
+          flutterRoot: p.join(d.sandbox, 'flutter'),
+          flutterVersion: '1.2.3',
+        ).validate();
       });
 
-      test("the SDK doesn't contain the package", () async {
+      test('gets an SDK dependency from bin/cache/pkg', () async {
         await d.appDir(
           dependencies: {
-            'bar': {'sdk': 'flutter'},
+            'baz': {'sdk': 'flutter'},
           },
         ).create();
         await pubCommand(
           command,
           environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
-          error: equalsIgnoringWhitespace("""
-              Because myapp depends on bar from sdk which doesn't exist
-                (could not find package bar in the Flutter SDK), version solving
-                failed.
-            """),
-          exitCode: exit_codes.UNAVAILABLE,
         );
+
+        await d.appPackageConfigFile(
+          [
+            d.packageConfigEntry(
+              name: 'baz',
+              path: p.join(d.sandbox, 'flutter', 'bin', 'cache', 'pkg', 'baz'),
+            ),
+          ],
+          flutterRoot: p.join(d.sandbox, 'flutter'),
+          flutterVersion: '1.2.3',
+        ).validate();
       });
 
-      test("the Dart SDK doesn't contain the package", () async {
+      test('unlocks an SDK dependency when the version changes', () async {
         await d.appDir(
           dependencies: {
-            'bar': {'sdk': 'dart'},
+            'foo': {'sdk': 'flutter'},
           },
         ).create();
         await pubCommand(
           command,
-          error: equalsIgnoringWhitespace("""
+          environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
+        );
+
+        await d
+            .file(
+              '$appPath/pubspec.lock',
+              allOf([contains('0.0.1'), isNot(contains('0.0.2'))]),
+            )
+            .validate();
+
+        await d.dir(
+          'flutter/packages/foo',
+          [d.libPubspec('foo', '0.0.2')],
+        ).create();
+        await pubCommand(
+          command,
+          environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
+        );
+
+        await d
+            .file(
+              '$appPath/pubspec.lock',
+              allOf([isNot(contains('0.0.1')), contains('0.0.2')]),
+            )
+            .validate();
+      });
+
+      // Regression test for #1883
+      test(
+          "doesn't fail if the Flutter SDK's version file doesn't exist when "
+          'nothing depends on Flutter', () async {
+        await d.appDir().create();
+        deleteEntry(
+          p.join(d.sandbox, 'flutter', 'bin', 'cache', 'flutterVersion'),
+        );
+        await pubCommand(
+          command,
+          environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
+        );
+        await d.appPackageConfigFile(
+          [],
+          flutterRoot: p.join(d.sandbox, 'flutter'),
+          flutterVersion: '1.2.3',
+        ).validate();
+      });
+
+      group('fails if', () {
+        test("the version constraint doesn't match", () async {
+          await d.appDir(
+            dependencies: {
+              'foo': {'sdk': 'flutter', 'version': '^1.0.0'},
+            },
+          ).create();
+          await pubCommand(
+            command,
+            environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
+            error: contains('''
+Because myapp depends on foo ^1.0.0 from sdk which doesn't match any versions, version solving failed.'''),
+          );
+        });
+
+        test('the SDK is unknown', () async {
+          await d.appDir(
+            dependencies: {
+              'foo': {'sdk': 'unknown'},
+            },
+          ).create();
+          await pubCommand(
+            command,
+            error: equalsIgnoringWhitespace('''
+Because myapp depends on foo from sdk which doesn't exist (unknown SDK "unknown"), version solving failed.'''),
+            exitCode: exit_codes.UNAVAILABLE,
+          );
+        });
+
+        test('the SDK is unavailable', () async {
+          await d.appDir(
+            dependencies: {
+              'foo': {'sdk': 'flutter'},
+            },
+          ).create();
+          await pubCommand(
+            command,
+            error: equalsIgnoringWhitespace("""
+              Because myapp depends on foo from sdk which doesn't exist (the
+                Flutter SDK is not available), version solving failed.
+
+              Flutter users should use `flutter pub` instead of `dart pub`.
+            """),
+            exitCode: exit_codes.UNAVAILABLE,
+          );
+        });
+
+        test("the SDK doesn't contain the package", () async {
+          await d.appDir(
+            dependencies: {
+              'bar': {'sdk': 'flutter'},
+            },
+          ).create();
+          await pubCommand(
+            command,
+            environment: {'FLUTTER_ROOT': p.join(d.sandbox, 'flutter')},
+            error: equalsIgnoringWhitespace("""
+              Because myapp depends on bar from sdk which doesn't exist
+                (could not find package bar in the Flutter SDK), version solving
+                failed.
+            """),
+            exitCode: exit_codes.UNAVAILABLE,
+          );
+        });
+
+        test("the Dart SDK doesn't contain the package", () async {
+          await d.appDir(
+            dependencies: {
+              'bar': {'sdk': 'dart'},
+            },
+          ).create();
+          await pubCommand(
+            command,
+            error: equalsIgnoringWhitespace("""
               Because myapp depends on bar from sdk which doesn't exist
                 (could not find package bar in the Dart SDK), version solving
                 failed.
             """),
-          exitCode: exit_codes.UNAVAILABLE,
+            exitCode: exit_codes.UNAVAILABLE,
+          );
+        });
+      });
+
+      test('supports the Fuchsia SDK', () async {
+        renameDir(p.join(d.sandbox, 'flutter'), p.join(d.sandbox, 'fuchsia'));
+
+        await d.appDir(
+          dependencies: {
+            'foo': {'sdk': 'fuchsia'},
+          },
+        ).create();
+        await pubCommand(
+          command,
+          environment: {'FUCHSIA_DART_SDK_ROOT': p.join(d.sandbox, 'fuchsia')},
         );
+        await d.appPackageConfigFile([
+          d.packageConfigEntry(
+            name: 'foo',
+            path: p.join(d.sandbox, 'fuchsia', 'packages', 'foo'),
+          ),
+          d.packageConfigEntry(name: 'bar', version: '1.0.0'),
+        ]).validate();
       });
     });
 
-    test('supports the Fuchsia SDK', () async {
-      renameDir(p.join(d.sandbox, 'flutter'), p.join(d.sandbox, 'fuchsia'));
+    group('dart', () {
+      group('with valid SDK configuration', () {
+        setUp(() async {
+          final server = await servePackages();
+          server.serve('bar', '1.0.0');
 
-      await d.appDir(
-        dependencies: {
-          'foo': {'sdk': 'fuchsia'},
-        },
-      ).create();
-      await pubCommand(
-        command,
-        environment: {'FUCHSIA_DART_SDK_ROOT': p.join(d.sandbox, 'fuchsia')},
-      );
-      await d.appPackageConfigFile([
-        d.packageConfigEntry(
-          name: 'foo',
-          path: p.join(d.sandbox, 'fuchsia', 'packages', 'foo'),
-        ),
-        d.packageConfigEntry(name: 'bar', version: '1.0.0'),
-      ]).validate();
+          await d.dir('dart', [
+            d.dir('packages', [
+              d.dir('foo', [
+                d.libDir('foo', 'foo 0.0.1'),
+                d.libPubspec('foo', '0.0.1', deps: {}),
+              ]),
+            ]),
+            d.sdkPackagesConfig(
+              SdkPackageConfig(
+                'dart',
+                {'foo': SdkPackage('foo', 'packages/foo')},
+                1,
+              ),
+            ),
+          ]).create();
+        });
+
+        test('gets an SDK dependency from sdk_packages.yaml', () async {
+          await d.appDir(
+            dependencies: {
+              'foo': {'sdk': 'dart', 'version': '^0.0.1'},
+            },
+          ).create();
+
+          await pubCommand(
+            command,
+            environment: {'DART_ROOT': p.join(d.sandbox, 'dart')},
+          );
+
+          await d.appPackageConfigFile([
+            d.packageConfigEntry(
+              name: 'foo',
+              path: p.join(d.sandbox, 'dart', 'packages', 'foo'),
+              version: '0.0.1',
+            ),
+          ]).validate();
+        });
+
+        test(
+            'fails if the version range isn\'t compatible with the SDK '
+            'dependency from sdk_packages.yaml', () async {
+          await d.appDir(
+            dependencies: {
+              'foo': {'sdk': 'dart', 'version': '^1.0.0'},
+            },
+          ).create();
+
+          await pubCommand(
+            command,
+            environment: {'DART_ROOT': p.join(d.sandbox, 'dart')},
+            error: equalsIgnoringWhitespace('''
+             Because myapp depends on foo ^1.0.0 from sdk which doesn't match
+             any versions, version solving failed.
+
+             You can try the following suggestion to make the pubspec resolve:
+
+             * Try updating the following constraints: dart pub add
+               foo:'{"version":"^0.0.1","sdk":"dart"}'
+            '''),
+          );
+        });
+      });
+
+      test('does not allow non-SDK deps in SDK packages', () async {
+        final server = await servePackages();
+        server.serve('bar', '1.0.0');
+
+        await d.dir('dart', [
+          d.dir('packages', [
+            d.dir('foo', [
+              d.libDir('foo', 'foo 0.0.1'),
+              d.libPubspec('foo', '0.0.1', deps: {'bar': '^1.0.0'}),
+            ]),
+          ]),
+          d.sdkPackagesConfig(
+            SdkPackageConfig(
+              'dart',
+              {'foo': SdkPackage('foo', 'packages/foo')},
+              1,
+            ),
+          ),
+        ]).create();
+
+        await d.appDir(
+          dependencies: {
+            'foo': {'sdk': 'dart', 'version': '^1.0.0'},
+          },
+        ).create();
+
+        await pubCommand(
+          command,
+          environment: {'DART_ROOT': p.join(d.sandbox, 'dart')},
+          error: contains(
+              'Unsupported operation: Only SDK packages are allowed as regular '
+              'dependencies for packages vendored by the dart SDK, but the `foo` '
+              'package has a hosted dependency on `bar`.'),
+        );
+      });
+
+      test('expects a value of `dart` for the `sdk` field', () async {
+        final server = await servePackages();
+        server.serve('bar', '1.0.0');
+
+        await d.dir('dart', [
+          d.dir('packages', [
+            d.dir('foo', [
+              d.libDir('foo', 'foo 0.0.1'),
+              d.libPubspec('foo', '0.0.1', deps: {}),
+            ]),
+          ]),
+          d.sdkPackagesConfig(
+            SdkPackageConfig(
+              'fuschia',
+              {'foo': SdkPackage('foo', 'packages/foo')},
+              1,
+            ),
+          ),
+        ]).create();
+
+        await d.appDir(
+          dependencies: {
+            'foo': {'sdk': 'dart', 'version': '^1.0.0'},
+          },
+        ).create();
+
+        await pubCommand(
+          command,
+          environment: {'DART_ROOT': p.join(d.sandbox, 'dart')},
+          error: contains(
+              'Expected a configuration for the `dart` sdk but got one for '
+              '`fuschia`. (at character 8)'),
+          exitCode: 65,
+        );
+      });
     });
   });
 }