blob: dac8769bb5f2605c60e345a1d185568398c4e26f [file] [edit]
// Copyright (c) 2025, 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.
@TestOn('vm')
library;
import 'dart:convert';
import 'dart:io';
import 'package:path/path.dart' as p;
import 'package:pub/src/exit_codes.dart';
import 'package:test/test.dart';
import 'package:test_descriptor/test_descriptor.dart';
import 'descriptor.dart' as d;
import 'test_pub.dart';
Future<void> main() async {
test('allows experiments that are enabled in the root', () async {
final server = await servePackages();
await _setupSdks();
server.serve(
'foo',
'1.0.0',
pubspec: {
'experiments': {
'enable': ['abc'],
},
},
);
await d
.appDir(
dependencies: {'foo': '^1.0.0'},
pubspec: {
'experiments': {
'enable': ['abc'],
},
},
)
.create();
await pubGet(
output: contains('''
Experiments enabled:
* `abc` for foo, myapp - New alphabetical feature
See https://dart.dev/go/experiments for more information.'''),
environment: _environment,
);
final packageConfig = _readPackageConfig();
expect(packageConfig.containsKey('experiments'), isFalse);
expect(_experimentsByPackage(packageConfig), {
'foo': ['abc'],
'myapp': ['abc'],
});
});
test('writes the experiments of each workspace package to its own '
'package_config entry', () async {
final server = await servePackages();
await _setupSdks();
server.serve(
'foo',
'1.0.0',
pubspec: {
'experiments': {
'enable': ['abc'],
},
},
);
await d.dir(appPath, [
d.libPubspec(
'myapp',
'1.0.0',
sdk: '^3.5.0',
extras: {
'workspace': ['pkgs/a', 'pkgs/b'],
},
),
d.dir('pkgs', [
d.dir('a', [
d.libPubspec(
'a',
'1.0.0',
deps: {'foo': '^1.0.0'},
resolutionWorkspace: true,
extras: {
'experiments': {
'enable': ['abc'],
},
},
),
]),
d.dir('b', [d.libPubspec('b', '1.0.0', resolutionWorkspace: true)]),
]),
]).create();
await pubGet(
output: contains('* `abc` for a, foo - New alphabetical feature'),
environment: {..._environment, '_PUB_TEST_SDK_VERSION': '3.5.0'},
);
// `b` and `myapp` don't opt in, so tools must not enable `abc` for them.
expect(_experimentsByPackage(_readPackageConfig()), {
'a': ['abc'],
'foo': ['abc'],
});
});
test('Finds the version with the right experiments enabled', () async {
final server = await servePackages();
await _setupSdks();
server.serve(
'foo',
'1.0.0-dev',
pubspec: {
'experiments': {
'enable': ['abc'],
},
},
);
server.serve(
'foo',
'1.0.1-dev', // This version is newer, but uses a disabled experiment.
pubspec: {
'experiments': {
'enable': ['abcd'],
},
},
);
await d
.appDir(
dependencies: {'foo': '^1.0.0-dev'},
pubspec: {
'experiments': {
'enable': ['abc'],
},
},
)
.create();
await pubGet(
output: contains('+ foo 1.0.0-dev'),
environment: _environment,
);
});
test('disallows experiments that are not enabled in the root', () async {
final server = await servePackages();
await _setupSdks();
server.serve(
'foo',
'1.1.0-dev',
pubspec: {
'experiments': {
'enable': ['abc'],
},
},
);
await d.appDir(dependencies: {'foo': '^1.0.0-dev'}).create();
await pubGet(
error: '''
Because myapp depends on foo any which requires enabling the experiment `abc`, version solving failed.
The experiment `abc` has not been enabled.
Currently no experiments are enabled.
To enable it add to your pubspec.yaml:
```
experiments:
enable:
- abc
```
Read more about experiments at https://dart.dev/go/experiments.''',
environment: _environment,
);
});
test('disallows path dependencies using experiments that are not enabled '
'in the root', () async {
await servePackages();
await _setupSdks();
await d.dir('foo', [
d.libPubspec(
'foo',
'1.0.0',
extras: {
'experiments': {
'enable': ['abc'],
},
},
),
]).create();
await d
.appDir(
dependencies: {
'foo': {'path': '../foo'},
},
)
.create();
await pubGet(
error: contains('The experiment `abc` has not been enabled.'),
environment: _environment,
);
});
test('allows dependencies to use experiments that are enabled by '
'default', () async {
final server = await servePackages();
await _setupSdks();
server.serve(
'foo',
'1.0.0',
pubspec: {
'experiments': {
'enable': ['shipped'],
},
},
);
await d.appDir(dependencies: {'foo': '^1.0.0'}).create();
await pubGet(
output: allOf(
contains('+ foo 1.0.0'),
isNot(contains('Experiments enabled')),
),
environment: _environment,
);
expect(_experimentsByPackage(_readPackageConfig()), isEmpty);
});
test('disallows experiments that are not enabled in the sdk', () async {
await servePackages();
await _setupSdks();
await d
.appDir(
pubspec: {
'experiments': {
'enable': <String>['abcd'],
},
},
)
.create();
await pubGet(
error: contains('''
`abcd` is not a known experiment.
Available experiments are:
* `abc`: New alphabetical feature (https://dart.dev/experiments/abc)
* `main-only`: Only on main
* `shipped`: Already shipped
Read more about experiments at https://dart.dev/go/experiments.'''),
environment: _environment,
exitCode: DATA,
);
});
test('disallows experiments listed more than once', () async {
await servePackages();
await _setupSdks();
await d
.appDir(
pubspec: {
'experiments': {
'enable': ['abc', 'abc'],
},
},
)
.create();
await pubGet(
error: contains('The experiment `abc` is listed more than once.'),
environment: _environment,
exitCode: DATA,
);
});
test('disallows experiments that are not available on the channel of the '
'sdk', () async {
await servePackages();
await _setupSdks();
await d
.appDir(
pubspec: {
'experiments': {
'enable': ['main-only'],
},
},
)
.create();
await pubGet(
error: contains(
'The experiment `main-only` is only available on the main channel(s). '
'This SDK is on the stable channel.',
),
environment: {..._environment, '_PUB_TEST_SDK_CHANNEL': 'stable'},
exitCode: DATA,
);
await pubGet(
environment: {..._environment, '_PUB_TEST_SDK_CHANNEL': 'main'},
);
expect(_experimentsByPackage(_readPackageConfig()), {
'myapp': ['main-only'],
});
});
test('reads the experiments known by the Dart SDK', () async {
await servePackages();
await _setupSdks(
dartExperiments: [
{'name': 'dart-feature', 'description': 'A Dart feature'},
],
);
await d
.appDir(
pubspec: {
'experiments': {
'enable': ['dart-feature'],
},
},
)
.create();
await pubGet(
output: contains('* `dart-feature` for myapp - A Dart feature'),
environment: _environment,
);
expect(_experimentsByPackage(_readPackageConfig()), {
'myapp': ['dart-feature'],
});
});
test('reads the experiments file in the format generated by the Dart '
'SDK', () async {
await servePackages();
// Entries copied from sdk/lib/_internal/sdk_experiments.json in the Dart
// SDK, which is generated by
// pkg/front_end/tool/generate_experimental_flags.dart.
await _setupSdks(
dartExperimentsFile: {
'_comment':
'Generated from tools/experimental_features.yaml by '
"'dart pkg/front_end/tool/cfe.dart generate-experimental-flags'. "
'Do not edit.',
'version': 1,
'experiments': [
{
'name': 'class-modifiers',
'description': 'Class modifiers',
'enabledIn': '3.0.0',
'expired': true,
'experimentalReleaseVersion': '3.0.0',
},
{
'name': 'data-assets',
'description': 'Enable data assets in hooks.',
'channels': ['main', 'dev'],
'experimentalReleaseVersion': '3.14.0',
},
{
'name': 'variance',
'description': 'Sound variance',
'experimentalReleaseVersion': '3.14.0',
},
],
},
);
await d
.appDir(
pubspec: {
'experiments': {
'enable': ['data-assets', 'variance'],
},
},
)
.create();
await pubGet(
error: contains(
'The experiment `data-assets` is only available on the main, dev '
'channel(s). This SDK is on the stable channel.',
),
environment: {..._environment, '_PUB_TEST_SDK_CHANNEL': 'stable'},
exitCode: DATA,
);
await pubGet(
output: allOf(
contains('* `data-assets` for myapp - Enable data assets in hooks.'),
contains('* `variance` for myapp - Sound variance'),
),
environment: {..._environment, '_PUB_TEST_SDK_CHANNEL': 'dev'},
);
expect(_experimentsByPackage(_readPackageConfig()), {
'myapp': ['data-assets', 'variance'],
});
});
test('rejects an experiments file with an unknown version', () async {
await servePackages();
await _setupSdks(
dartExperimentsFile: {
'version': 2,
'experiments': [
{'name': 'dart-feature', 'description': 'A Dart feature'},
],
},
);
await d
.appDir(
pubspec: {
'experiments': {
'enable': ['dart-feature'],
},
},
)
.create();
await pubGet(
error: allOf(
contains('has version 2'),
contains('This version of pub only understands version 1.'),
),
environment: _environment,
exitCode: 1,
);
});
test('warns about expired experiments and does not pass them on', () async {
final server = await servePackages();
await _setupSdks(
dartExperiments: [
{
'name': 'shipped-and-expired',
'description': 'Shipped a while ago',
'enabledIn': '3.0.0',
'expired': true,
},
{'name': 'abandoned', 'description': 'Never shipped', 'expired': true},
],
);
// Dependencies may list expired experiments that the root doesn't, and
// pub doesn't warn about them.
server.serve(
'foo',
'1.0.0',
pubspec: {
'experiments': {
'enable': ['abandoned'],
},
},
);
await d
.appDir(
dependencies: {'foo': '^1.0.0'},
pubspec: {
'experiments': {
'enable': ['shipped-and-expired'],
},
},
)
.create();
await pubGet(
warning: allOf(
contains(
'The experiment `shipped-and-expired` has been enabled by default '
'since Dart 3.0.0. Remove it from `experiments` in the pubspec.yaml '
'of myapp.',
),
isNot(contains('`abandoned`')),
),
environment: _environment,
);
expect(_experimentsByPackage(_readPackageConfig()), isEmpty);
await d
.appDir(
dependencies: {'foo': '^1.0.0'},
pubspec: {
'experiments': {
'enable': ['abandoned'],
},
},
)
.create();
await pubGet(
warning: contains(
'The experiment `abandoned` has been retired and no longer has any '
'effect. Remove it from `experiments` in the pubspec.yaml of myapp.',
),
environment: _environment,
);
await d
.appDir(
dependencies: {'foo': '^1.0.0'},
pubspec: {
'experiments': {
'enable': ['no-shipped-and-expired'],
},
},
)
.create();
await pubGet(
warning: contains(
'The experiment `shipped-and-expired` has been enabled by default '
'since Dart 3.0.0 and can no longer be disabled. Remove '
'`no-shipped-and-expired` from `experiments` in the pubspec.yaml of '
'myapp.',
),
environment: _environment,
);
expect(_experimentsByPackage(_readPackageConfig()), isEmpty);
});
test('can opt out of an experiment enabled by default', () async {
final server = await servePackages();
await _setupSdks();
// A dependency can opt out of a default-enabled experiment without the
// root package having to list it.
server.serve(
'foo',
'1.0.0',
pubspec: {
'experiments': {
'enable': ['no-shipped'],
},
},
);
await d
.appDir(
dependencies: {'foo': '^1.0.0'},
pubspec: {
'experiments': {
'enable': ['no-shipped', 'no-abc'],
},
},
)
.create();
await pubGet(
output: allOf(
contains('* `no-shipped` for foo, myapp - Already shipped'),
isNot(contains('no-abc')),
),
environment: _environment,
);
expect(_experimentsByPackage(_readPackageConfig()), {
'foo': ['no-shipped'],
'myapp': ['no-shipped'],
});
});
test('rejects enabling and disabling the same experiment', () async {
await _setupSdks();
await d
.appDir(
pubspec: {
'experiments': {
'enable': ['shipped', 'no-shipped'],
},
},
)
.create();
await pubGet(
error: contains(
'The experiment `shipped` cannot be both enabled and disabled.',
),
environment: _environment,
exitCode: DATA,
);
});
test('Can global activate a package using experiments', () async {
final server = await servePackages();
server.serve(
'foo',
'1.0.0',
pubspec: {
'experiments': {
'enable': ['abc'],
},
},
);
await _setupSdks();
await runPub(
args: ['global', 'activate', 'foo', '--experiments', 'abc'],
output: contains('* `abc` for foo - New alphabetical feature'),
environment: _environment,
);
});
test(
'supports package-scoped experiments (<package>.<experiment>)',
() async {
final server = await servePackages();
await _setupSdks();
server.serve(
'foo',
'1.0.0',
pubspec: {
'experiments': {
'declare': {
'new_api': {
'description': 'New experimental API',
'docUrl': 'https://example.com/new_api',
},
'graduated_api': {
'description': 'Graduated API',
'enabledIn': '0.9.0',
},
'retired_api': {'description': 'Retired API', 'expired': true},
},
},
},
);
server.serve(
'bar',
'1.0.0-dev',
deps: {'foo': '^1.0.0'},
pubspec: {
'experiments': {
'enable': ['foo.new_api', 'foo.graduated_api'],
},
},
);
// Fails when `bar` requires `foo.new_api` and root does not opt in.
await d.appDir(dependencies: {'bar': '^1.0.0-dev'}).create();
await pubGet(
error: contains('The experiment `foo.new_api` has not been enabled.'),
environment: _environment,
);
// Succeeds when root opts into `foo.new_api` (using map syntax with
// both `enable` and `declare`). Graduated and retired experiments emit
// warnings and are omitted from package_config.json.
await d
.appDir(
dependencies: {'bar': '^1.0.0-dev'},
pubspec: {
'experiments': {
'enable': [
'foo.new_api',
'foo.graduated_api',
'foo.retired_api',
],
'declare': {
'app_exp': {'description': 'App experiment'},
},
},
},
)
.create();
await pubGet(
output: contains(
'* `foo.new_api` for bar, myapp - New experimental API',
),
warning: allOf(
contains(
'The experiment `foo.graduated_api` has been enabled by default '
'since foo 0.9.0. Remove it from `experiments` in the pubspec.yaml '
'of myapp.',
),
contains(
'The experiment `foo.retired_api` has been retired and no longer '
'has any effect. Remove it from `experiments` in the pubspec.yaml '
'of myapp.',
),
),
environment: _environment,
);
expect(_experimentsByPackage(_readPackageConfig()), {
'bar': ['foo.new_api'],
'myapp': ['foo.new_api'],
});
// A dependency that only lists graduated/retired package experiments does
// not require the root package to opt in.
server.serve(
'baz',
'1.0.0',
deps: {'foo': '^1.0.0'},
pubspec: {
'experiments': {
'enable': ['foo.graduated_api', 'foo.retired_api'],
},
},
);
await d.appDir(dependencies: {'baz': '^1.0.0'}).create();
await pubGet(
output: allOf(
contains('+ baz 1.0.0'),
isNot(contains('Experiments enabled')),
),
environment: _environment,
);
expect(_experimentsByPackage(_readPackageConfig()), isEmpty);
},
);
test('selects version where package experiment has graduated, or fails if '
'constrained to ungraduated version without opt-in', () async {
final server = await servePackages();
await _setupSdks();
server.serve(
'foo',
'0.8.0',
pubspec: {
'experiments': {
'declare': {
'graduated_api': {'description': 'Experimental in 0.8.0'},
},
},
},
);
server.serve(
'foo',
'1.0.0',
pubspec: {
'experiments': {
'declare': {
'graduated_api': {
'description': 'Graduated in 0.9.0',
'enabledIn': '0.9.0',
},
},
},
},
);
server.serve(
'bar',
'1.0.0-dev',
deps: {'foo': '>=0.8.0 <2.0.0'},
pubspec: {
'experiments': {
'enable': ['foo.graduated_api'],
},
},
);
// When `myapp` pins `foo: 0.8.0` without opting in to
// `foo.graduated_api`, solving fails with the experiment hint.
await d
.appDir(dependencies: {'bar': '^1.0.0-dev', 'foo': '0.8.0'})
.create();
await pubGet(
error: contains(
'The experiment `foo.graduated_api` has not been enabled.',
),
environment: _environment,
);
// When `myapp` allows `foo: ^1.0.0` (where `graduated_api` is graduated),
// solving succeeds without opt-in.
await d
.appDir(dependencies: {'bar': '^1.0.0-dev', 'foo': '>=0.8.0 <2.0.0'})
.create();
await pubGet(output: contains('+ foo 1.0.0'), environment: _environment);
expect(_experimentsByPackage(_readPackageConfig()), isEmpty);
});
test('supports intra-workspace declared experiments', () async {
await servePackages();
await _setupSdks();
await d.dir(appPath, [
d.libPubspec(
'myapp',
'1.0.0',
sdk: '^3.5.0',
extras: {
'workspace': ['pkgs/a', 'pkgs/b'],
},
),
d.dir('pkgs', [
d.dir('a', [
d.libPubspec(
'a',
'1.0.0',
resolutionWorkspace: true,
extras: {
'experiments': {
'declare': {
'exp_one': {'description': 'Workspace experiment one'},
'exp_graduated': {
'description': 'Graduated workspace experiment',
'enabledIn': '1.0.0',
},
},
},
},
),
]),
d.dir('b', [
d.libPubspec(
'b',
'1.0.0',
deps: {'a': '^1.0.0'},
resolutionWorkspace: true,
extras: {
'experiments': {
'enable': ['a.exp_one', 'a.exp_graduated'],
},
},
),
]),
]),
]).create();
await pubGet(
output: contains('* `a.exp_one` for b - Workspace experiment one'),
warning: contains(
'The experiment `a.exp_graduated` has been enabled by default since a '
'1.0.0. Remove it from `experiments` in the pubspec.yaml of b.',
),
environment: {..._environment, '_PUB_TEST_SDK_VERSION': '3.5.0'},
);
expect(_experimentsByPackage(_readPackageConfig()), {
'b': ['a.exp_one'],
});
});
test(
'errors when a package-scoped experiment references a missing package or '
'undeclared experiment',
() async {
final server = await servePackages();
await _setupSdks();
server.serve(
'foo',
'1.0.0',
pubspec: {
'experiments': {
'declare': {
'new_api': {
'description': 'New experimental API',
'docUrl': 'https://example.com/new_api',
},
},
},
},
);
server.serve('plain_pkg', '1.0.0');
await d
.appDir(
pubspec: {
'experiments': {
'enable': ['missing_pkg.new_api'],
},
},
)
.create();
await pubGet(
error: contains(
'The experiment `missing_pkg.new_api` in the pubspec.yaml of myapp '
'refers to package `missing_pkg`, which is not in the dependency '
'graph.',
),
environment: _environment,
exitCode: DATA,
);
await d
.appDir(
dependencies: {'foo': '^1.0.0'},
pubspec: {
'experiments': {
'enable': ['foo.unknown_api'],
},
},
)
.create();
await pubGet(
error: contains(
'`foo.unknown_api` is not a known experiment of package `foo` '
'(1.0.0).\n\n'
'Available experiments in `foo` (1.0.0) are:\n'
'* `foo.new_api`: New experimental API (https://example.com/new_api)',
),
environment: _environment,
exitCode: DATA,
);
await d
.appDir(
dependencies: {'plain_pkg': '^1.0.0'},
pubspec: {
'experiments': {
'enable': ['plain_pkg.some_api'],
},
},
)
.create();
await pubGet(
error: contains(
'`plain_pkg.some_api` is not a known experiment of package '
'`plain_pkg` (1.0.0).\n\n'
'Package `plain_pkg` (1.0.0) does not declare any experiments.',
),
environment: _environment,
exitCode: DATA,
);
},
);
test(
'rejects malformed package-scoped experiment names and declare schema',
() async {
await _setupSdks();
for (final bad in ['no-foo.bar', 'foo.', '.bar', 'foo.bar.baz']) {
await d
.appDir(
pubspec: {
'experiments': {
'enable': [bad],
},
},
)
.create();
await pubGet(
error: contains(
'Package experiment `$bad` must have the form '
'`<package>.<experiment>`.',
),
environment: _environment,
exitCode: DATA,
);
}
for (final badDeclared in ['no-exp', 'foo.bar']) {
await d
.appDir(
pubspec: {
'experiments': {
'declare': {
badDeclared: {'description': 'Bad name'},
},
},
},
)
.create();
await pubGet(
error: contains(
'Declared experiment name must be a valid identifier '
'(matching `^[a-zA-Z0-9_-]+\$` and not starting with `no-`).',
),
environment: _environment,
exitCode: DATA,
);
}
final invalidSchemas = <(Object, String)>[
(
<String>['abc'],
'`experiments` must be a mapping with `enable` and/or `declare` '
'keys.',
),
(
123,
'`experiments` must be a mapping with `enable` and/or `declare` '
'keys.',
),
(
{'unknown': <String>[]},
'`experiments` mapping may only contain `enable` and `declare` keys.',
),
(
{'enable': 'not-a-list'},
'`experiments.enable` must be a list of strings',
),
(
{
'enable': [123],
},
'`experiments.enable` must be a list of strings',
),
({'declare': <String>[]}, '`experiments.declare` must be a mapping'),
(
{
'declare': {'exp': 'not-a-map'},
},
'Declared experiment `exp` must be a mapping.',
),
(
{
'declare': {
'exp': {'description': 'ok', 'bogus': 1},
},
},
'Unknown field `bogus` in declared experiment `exp`.',
),
(
{
'declare': {'exp': <String, Object?>{}},
},
'Declared experiment `exp` must have a string "description".',
),
(
{
'declare': {
'exp': {'description': 'ok', 'docUrl': 123},
},
},
'"docUrl" of declared experiment `exp` must be a string.',
),
(
{
'declare': {
'exp': {'description': 'ok', 'enabledIn': 123},
},
},
'"enabledIn" of declared experiment `exp` must be a version string.',
),
(
{
'declare': {
'exp': {'description': 'ok', 'enabledIn': 'not-a-version'},
},
},
'Invalid "enabledIn" version in declared experiment `exp`:',
),
(
{
'declare': {
'exp': {'description': 'ok', 'expired': 'not-a-bool'},
},
},
'"expired" of declared experiment `exp` must be a boolean.',
),
];
for (final (schema, expectedError) in invalidSchemas) {
await d.appDir(pubspec: {'experiments': schema}).create();
await pubGet(
error: contains(expectedError),
environment: _environment,
exitCode: DATA,
);
}
},
);
test(
'solver error reporting handles multiple disallowed experiments, '
'channel-restricted experiments, and already-enabled experiments',
() async {
final server = await servePackages();
await _setupSdks(
dartExperiments: [
{'name': 'def', 'description': 'Second experiment'},
{'name': 'ghi', 'description': 'Third experiment'},
],
);
// 1. Dependency requires multiple disallowed experiments (`def`, `ghi`)
// while root already has `abc` enabled.
server.serve(
'foo',
'1.0.0-dev',
pubspec: {
'experiments': {
'enable': ['def', 'ghi'],
},
},
);
await d
.appDir(
dependencies: {'foo': '^1.0.0-dev'},
pubspec: {
'experiments': {
'enable': ['abc'],
},
},
)
.create();
await pubGet(
error: '''
Because myapp depends on foo any which requires enabling the experiments `def`, `ghi`, version solving failed.
The experiments `def`, `ghi` have not been enabled.
Currently the following experiments are enabled: `abc`.
To enable them add to your pubspec.yaml:
```
experiments:
enable:
- abc
- def
- ghi
```
Read more about experiments at https://dart.dev/go/experiments.''',
environment: _environment,
);
// 2. Dependency requires an experiment (`main-only`) that is unavailable
// on the current SDK channel (`stable`).
server.serve(
'bar',
'1.0.0-dev',
pubspec: {
'experiments': {
'enable': ['main-only'],
},
},
);
await d.appDir(dependencies: {'bar': '^1.0.0-dev'}).create();
await pubGet(
error: '''
Because myapp depends on bar any which requires enabling the experiment `main-only`, version solving failed.
The experiment `main-only` is only available on the main channel(s). This SDK is on the stable channel.
Read more about experiments at https://dart.dev/go/experiments.''',
environment: {..._environment, '_PUB_TEST_SDK_CHANNEL': 'stable'},
);
},
);
}
/// The environment making pub use the SDKs created by [_setupSdks].
Map<String, String> get _environment => {
'FLUTTER_ROOT': p.join(sandbox, 'flutter'),
'DART_ROOT': p.join(sandbox, 'dart-sdk'),
};
/// Creates a fake Flutter SDK and a fake Dart SDK, each with an experiments
/// file.
///
/// The Flutter SDK knows the experiments `abc`, `main-only` (only on the main
/// channel) and `shipped` (enabled by default). The Dart SDK knows
/// [dartExperiments], unless [dartExperimentsFile] gives the full contents of
/// its experiments file.
Future<void> _setupSdks({
List<Map<String, Object?>> dartExperiments = const [],
Map<String, Object?>? dartExperimentsFile,
}) async {
await d.dir('flutter', [
d.flutterVersion('1.2.3'),
d.file(
'.sdk_experiments.json',
jsonEncode({
'experiments': [
{
'name': 'abc',
'description': 'New alphabetical feature',
'docUrl': 'https://dart.dev/experiments/abc',
},
{
'name': 'main-only',
'description': 'Only on main',
'channels': ['main'],
},
{
'name': 'shipped',
'description': 'Already shipped',
'enabledIn': '3.0.0',
},
],
}),
),
]).create();
await d.dir('dart-sdk', [
d.dir('lib', [
d.dir('_internal', [
d.file(
'sdk_experiments.json',
jsonEncode(dartExperimentsFile ?? {'experiments': dartExperiments}),
),
]),
]),
]).create();
}
Map<String, Object?> _readPackageConfig() =>
json.decode(
File(
p.join(sandbox, appPath, '.dart_tool', 'package_config.json'),
).readAsStringSync(),
)
as Map<String, Object?>;
/// The `experiments` of the entries in [packageConfig] that have any, keyed by
/// package name.
Map<String, Object?> _experimentsByPackage(
Map<String, Object?> packageConfig,
) => {
for (final entry in (packageConfig['packages']! as List).cast<Map>())
if (entry['experiments'] case final Object experiments)
entry['name'] as String: experiments,
};