// 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/compute_dev_dependencies.dart'; import 'package:flutter_tools/src/project.dart'; import 'package:package_config/package_config.dart'; import '../src/common.dart'; typedef Package = ({String name, List dependencies, List 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 `/pubspec.yaml`. void writePubspecs(List graph) { final Map packageConfigMap = {'configVersion': 2}; for (final Package 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'] ??= []) as List).add({ '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 graph) { fileSystem.file(fileSystem.path.join(graph.first.name, '.dart_tool', 'package_graph.json')) ..createSync(recursive: true) ..writeAsStringSync( jsonEncode({ 'configVersion': 1, 'packages': [ for (final Package package in graph) { '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 validatesComputeTransitiveDependencies( List graph, List exclusiveDevDependencies, ) async { writePubspecs(graph); writePackageGraph(graph); final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.directory('my_app')); final PackageConfig packageConfig = await loadPackageConfig(project.packageConfig); final List dependencies = computeTransitiveDependencies( project, packageConfig, fileSystem, ); expect(dependencies.map((Dependency d) => d.name), graph.map((Package p) => p.name).toSet()); for (final Package 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([ (name: 'my_app', dependencies: ['package_a'], devDependencies: []), (name: 'package_a', dependencies: ['package_b'], devDependencies: []), (name: 'package_b', dependencies: ['package_a'], devDependencies: []), ], []); }); test('dev dependency', () async { await validatesComputeTransitiveDependencies( [ ( name: 'my_app', dependencies: ['package_a'], devDependencies: ['package_b'], ), (name: 'package_a', dependencies: [], devDependencies: []), (name: 'package_b', dependencies: [], devDependencies: []), ], ['package_b'], ); }); test('dev used as a non-dev dependency transitively', () async { await validatesComputeTransitiveDependencies([ (name: 'my_app', dependencies: ['package_a'], devDependencies: ['package_b']), (name: 'package_a', dependencies: ['package_b'], devDependencies: []), (name: 'package_b', dependencies: [], devDependencies: []), ], []); }); test('combination of an included and excluded dev_dependency', () async { await validatesComputeTransitiveDependencies( [ ( name: 'my_app', dependencies: ['package_a'], devDependencies: ['package_b', 'package_c'], ), (name: 'package_a', dependencies: ['package_b'], devDependencies: []), (name: 'package_b', dependencies: [], devDependencies: []), (name: 'package_c', dependencies: [], devDependencies: []), ], ['package_c'], ); }); }