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,
+ );
+ });
});
});
}