blob: ad0fbe8d854a6da3d0a29607e22f63c3b7a5015b [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'dart:convert';
import 'package:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/package_graph.dart';
import 'package:flutter_tools/src/project.dart';
import 'package:package_config/package_config.dart';
import '../src/common.dart';
typedef Package = ({String name, List<String> dependencies, List<String> devDependencies});
// For all of these examples, imagine the following package structure:
//
// /
// /my_app
// pubspec.yaml
// /package_a
// pubspec.yaml
// /package_b
// pubspec.yaml
// /package_c
// pubspec.yaml
void main() {
late FileSystem fileSystem;
setUp(() {
Cache.flutterRoot = '';
fileSystem = MemoryFileSystem.test();
});
/// Write pubspec.yaml files on [fileSystem] for each of the packages in
/// [graph].
///
/// Each pubspec is stored in `<packagename>/pubspec.yaml`.
void writePubspecs(List<Package> graph) {
final packageConfigMap = <String, Object?>{'configVersion': 2};
for (final package in graph) {
fileSystem.file(fileSystem.path.join(package.name, 'pubspec.yaml'))
..createSync(recursive: true)
..writeAsStringSync('''
name: ${package.name}
dependencies:
${package.dependencies.map((String d) => ' $d: {path: ../$d}').join('\n')}
dev_dependencies:
${package.devDependencies.map((String d) => ' $d: {path: ../$d}').join('\n')}
''');
((packageConfigMap['packages'] ??= <Object?>[]) as List<Object?>).add(<String, Object?>{
'name': package.name,
'rootUri': '../../${package.name}',
'packageUri': 'lib/',
'languageVersion': '3.7',
});
}
fileSystem.file(fileSystem.path.join(graph.first.name, '.dart_tool', 'package_config.json'))
..createSync(recursive: true)
..writeAsStringSync(jsonEncode(packageConfigMap));
}
void writePackageGraph(List<Package> graph) {
fileSystem.file(fileSystem.path.join(graph.first.name, '.dart_tool', 'package_graph.json'))
..createSync(recursive: true)
..writeAsStringSync(
jsonEncode(<String, Object?>{
'configVersion': 1,
'packages': <Object?>[
for (final Package package in graph)
<String, Object?>{
'name': package.name,
'dependencies': package.dependencies,
'devDependencies': package.devDependencies,
},
],
}),
);
}
/// Validates basic properties of `computeTransitiveDependencies` when run on
/// pubspecs and package_config derrived from [graph] by `writePubspecs`.
///
/// Validates all dependencies are found.
///
/// And that exactly [exclusiveDevDependencies] are marked as
/// exclusiveDevDependency.
///
/// And that nothing is logged.
Future<void> validatesComputeTransitiveDependencies(
List<Package> graph,
List<String> exclusiveDevDependencies,
) async {
writePubspecs(graph);
writePackageGraph(graph);
final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.directory('my_app'));
final PackageConfig packageConfig = await loadPackageConfig(project.packageConfig);
final List<Dependency> dependencies = computeTransitiveDependencies(project, packageConfig);
expect(dependencies.map((Dependency d) => d.name), graph.map((Package p) => p.name).toSet());
for (final p in graph) {
expect(
dependencies.firstWhere((Dependency d) => d.name == p.name).isExclusiveDevDependency,
exclusiveDevDependencies.contains(p.name),
);
}
}
test('no dev dependencies at all', () async {
await validatesComputeTransitiveDependencies(<Package>[
(name: 'my_app', dependencies: <String>['package_a'], devDependencies: <String>[]),
(name: 'package_a', dependencies: <String>['package_b'], devDependencies: <String>[]),
(name: 'package_b', dependencies: <String>['package_a'], devDependencies: <String>[]),
], <String>[]);
});
test('dev dependency', () async {
await validatesComputeTransitiveDependencies(
<Package>[
(
name: 'my_app',
dependencies: <String>['package_a'],
devDependencies: <String>['package_b'],
),
(name: 'package_a', dependencies: <String>[], devDependencies: <String>[]),
(name: 'package_b', dependencies: <String>[], devDependencies: <String>[]),
],
<String>['package_b'],
);
});
test('dev used as a non-dev dependency transitively', () async {
await validatesComputeTransitiveDependencies(<Package>[
(name: 'my_app', dependencies: <String>['package_a'], devDependencies: <String>['package_b']),
(name: 'package_a', dependencies: <String>['package_b'], devDependencies: <String>[]),
(name: 'package_b', dependencies: <String>[], devDependencies: <String>[]),
], <String>[]);
});
test('combination of an included and excluded dev_dependency', () async {
await validatesComputeTransitiveDependencies(
<Package>[
(
name: 'my_app',
dependencies: <String>['package_a'],
devDependencies: <String>['package_b', 'package_c'],
),
(name: 'package_a', dependencies: <String>['package_b'], devDependencies: <String>[]),
(name: 'package_b', dependencies: <String>[], devDependencies: <String>[]),
(name: 'package_c', dependencies: <String>[], devDependencies: <String>[]),
],
<String>['package_c'],
);
});
}