mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Pub dependencies project validator (#106895)
This commit is contained in:
parent
da8070d6af
commit
cdf5b1d40d
@ -142,6 +142,7 @@ List<FlutterCommand> generateCommands({
|
||||
logger: globals.logger,
|
||||
terminal: globals.terminal,
|
||||
artifacts: globals.artifacts!,
|
||||
// new ProjectValidators should be added here for the --suggestions to run
|
||||
allProjectValidators: <ProjectValidator>[],
|
||||
),
|
||||
AssembleCommand(verboseHelp: verboseHelp, buildSystem: globals.buildSystem),
|
||||
|
||||
@ -138,10 +138,14 @@ class AnalyzeCommand extends FlutterCommand {
|
||||
}
|
||||
if (workingDirectory == null) {
|
||||
final Set<String> items = findDirectories(argResults!, _fileSystem);
|
||||
if (items.isEmpty || items.length > 1) {
|
||||
throwToolExit('The suggestions flags needs one directory path');
|
||||
if (items.isEmpty) { // user did not specify any path
|
||||
directoryPath = _fileSystem.currentDirectory.path;
|
||||
_logger.printTrace('Showing suggestions for current directory: $directoryPath');
|
||||
} else if (items.length > 1) { // if the user sends more than one path
|
||||
throwToolExit('The suggestions flag can process only one directory path');
|
||||
} else {
|
||||
directoryPath = items.first;
|
||||
}
|
||||
directoryPath = items.first;
|
||||
} else {
|
||||
directoryPath = workingDirectory!.path;
|
||||
}
|
||||
@ -150,6 +154,7 @@ class AnalyzeCommand extends FlutterCommand {
|
||||
logger: _logger,
|
||||
allProjectValidators: _allProjectValidators,
|
||||
userPath: directoryPath,
|
||||
processManager: _processManager,
|
||||
).run();
|
||||
} else if (boolArgDeprecated('watch')) {
|
||||
await AnalyzeContinuously(
|
||||
|
||||
@ -2,6 +2,8 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../project.dart';
|
||||
@ -15,7 +17,8 @@ class ValidateProject {
|
||||
required this.logger,
|
||||
required this.allProjectValidators,
|
||||
required this.userPath,
|
||||
this.verbose = false
|
||||
required this.processManager,
|
||||
this.verbose = false,
|
||||
});
|
||||
|
||||
final FileSystem fileSystem;
|
||||
@ -23,6 +26,7 @@ class ValidateProject {
|
||||
final bool verbose;
|
||||
final String userPath;
|
||||
final List<ProjectValidator> allProjectValidators;
|
||||
final ProcessManager processManager;
|
||||
|
||||
Future<FlutterCommandResult> run() async {
|
||||
final Directory workingDirectory = userPath.isEmpty ? fileSystem.currentDirectory : fileSystem.directory(userPath);
|
||||
|
||||
71
packages/flutter_tools/lib/src/dart_pub_json_formatter.dart
Normal file
71
packages/flutter_tools/lib/src/dart_pub_json_formatter.dart
Normal file
@ -0,0 +1,71 @@
|
||||
// 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:collection';
|
||||
|
||||
/// Parsing the output of "dart pub deps --json"
|
||||
///
|
||||
/// expected structure: {"name": "package name", "source": "hosted", "dependencies": [...]}
|
||||
class DartDependencyPackage {
|
||||
DartDependencyPackage({
|
||||
required this.name,
|
||||
required this.version,
|
||||
required this.source,
|
||||
required this.dependencies,
|
||||
});
|
||||
|
||||
factory DartDependencyPackage.fromHashMap(dynamic packageInfo) {
|
||||
String name = '';
|
||||
String version = '';
|
||||
String source = '';
|
||||
List<dynamic> dependencies = <dynamic>[];
|
||||
|
||||
if (packageInfo is LinkedHashMap) {
|
||||
final LinkedHashMap<String, dynamic> info = packageInfo as LinkedHashMap<String, dynamic>;
|
||||
if (info.containsKey('name')) {
|
||||
name = info['name'] as String;
|
||||
}
|
||||
if (info.containsKey('version')) {
|
||||
version = info['version'] as String;
|
||||
}
|
||||
if (info.containsKey('source')) {
|
||||
source = info['source'] as String;
|
||||
}
|
||||
if (info.containsKey('dependencies')) {
|
||||
dependencies = info['dependencies'] as List<dynamic>;
|
||||
}
|
||||
}
|
||||
return DartDependencyPackage(
|
||||
name: name,
|
||||
version: version,
|
||||
source: source,
|
||||
dependencies: dependencies.map((dynamic e) => e.toString()).toList(),
|
||||
);
|
||||
}
|
||||
|
||||
final String name;
|
||||
final String version;
|
||||
final String source;
|
||||
final List<String> dependencies;
|
||||
|
||||
}
|
||||
|
||||
class DartPubJson {
|
||||
DartPubJson(this._json);
|
||||
final LinkedHashMap<String, dynamic> _json;
|
||||
final List<DartDependencyPackage> _packages = <DartDependencyPackage>[];
|
||||
|
||||
List<DartDependencyPackage> get packages {
|
||||
if (_packages.isNotEmpty) {
|
||||
return _packages;
|
||||
}
|
||||
if (_json.containsKey('packages')) {
|
||||
final List<dynamic> packagesInfo = _json['packages'] as List<dynamic>;
|
||||
for (final dynamic info in packagesInfo) {
|
||||
_packages.add(DartDependencyPackage.fromHashMap(info));
|
||||
}
|
||||
}
|
||||
return _packages;
|
||||
}
|
||||
}
|
||||
@ -2,19 +2,23 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:collection';
|
||||
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import 'base/io.dart';
|
||||
import 'convert.dart';
|
||||
import 'dart_pub_json_formatter.dart';
|
||||
import 'flutter_manifest.dart';
|
||||
import 'project.dart';
|
||||
import 'project_validator_result.dart';
|
||||
|
||||
abstract class ProjectValidator {
|
||||
const ProjectValidator();
|
||||
String get title;
|
||||
bool supportsProject(FlutterProject project);
|
||||
/// Can return more than one result in case a file/command have a lot of info to share to the user
|
||||
Future<List<ProjectValidatorResult>> start(FlutterProject project);
|
||||
/// new ProjectValidators should be added here for the ValidateProjectCommand to run
|
||||
static List <ProjectValidator> allProjectValidators = <ProjectValidator>[
|
||||
GeneralInfoProjectValidator(),
|
||||
];
|
||||
}
|
||||
|
||||
/// Validator run for all platforms that extract information from the pubspec.yaml.
|
||||
@ -109,3 +113,77 @@ class GeneralInfoProjectValidator extends ProjectValidator{
|
||||
@override
|
||||
String get title => 'General Info';
|
||||
}
|
||||
|
||||
class PubDependenciesProjectValidator extends ProjectValidator {
|
||||
const PubDependenciesProjectValidator(this._processManager);
|
||||
final ProcessManager _processManager;
|
||||
|
||||
@override
|
||||
Future<List<ProjectValidatorResult>> start(FlutterProject project) async {
|
||||
const String name = 'Dart dependencies';
|
||||
final ProcessResult processResult = await _processManager.run(<String>['dart', 'pub', 'deps', '--json']);
|
||||
if (processResult.stdout is! String) {
|
||||
return <ProjectValidatorResult>[
|
||||
_createProjectValidatorError(name, 'Command dart pub deps --json failed')
|
||||
];
|
||||
}
|
||||
|
||||
final LinkedHashMap<String, dynamic> jsonResult;
|
||||
final List<ProjectValidatorResult> result = <ProjectValidatorResult>[];
|
||||
try {
|
||||
jsonResult = json.decode(
|
||||
processResult.stdout.toString()
|
||||
) as LinkedHashMap<String, dynamic>;
|
||||
} on FormatException{
|
||||
result.add(_createProjectValidatorError(name, processResult.stderr.toString()));
|
||||
return result;
|
||||
}
|
||||
|
||||
final DartPubJson dartPubJson = DartPubJson(jsonResult);
|
||||
final List <String> dependencies = <String>[];
|
||||
|
||||
// Information retrieved from the pubspec.lock file if a dependency comes from
|
||||
// the hosted url https://pub.dartlang.org we ignore it or if the package
|
||||
// is the current directory being analyzed (root).
|
||||
final Set<String> hostedDependencies = <String>{'hosted', 'root'};
|
||||
|
||||
for (final DartDependencyPackage package in dartPubJson.packages) {
|
||||
if (!hostedDependencies.contains(package.source)) {
|
||||
dependencies.addAll(package.dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
if (dependencies.isNotEmpty) {
|
||||
final String verb = dependencies.length == 1 ? 'is' : 'are';
|
||||
result.add(
|
||||
ProjectValidatorResult(
|
||||
name: name,
|
||||
value: '${dependencies.join(', ')} $verb not hosted',
|
||||
status: StatusProjectValidator.warning,
|
||||
)
|
||||
);
|
||||
} else {
|
||||
result.add(
|
||||
const ProjectValidatorResult(
|
||||
name: name,
|
||||
value: 'All pub dependencies are hosted on https://pub.dartlang.org',
|
||||
status: StatusProjectValidator.success,
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
bool supportsProject(FlutterProject project) {
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
String get title => 'Pub dependencies';
|
||||
|
||||
ProjectValidatorResult _createProjectValidatorError(String name, String value) {
|
||||
return ProjectValidatorResult(name: name, value: value, status: StatusProjectValidator.error);
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,77 @@
|
||||
// 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 'package:file/memory.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/project_validator.dart';
|
||||
import 'package:flutter_tools/src/project_validator_result.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
|
||||
void main() {
|
||||
late FileSystem fileSystem;
|
||||
|
||||
group('PubDependenciesProjectValidator', () {
|
||||
|
||||
setUp(() {
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
});
|
||||
|
||||
testWithoutContext('success when all dependencies are hosted', () async {
|
||||
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['dart', 'pub', 'deps', '--json'],
|
||||
stdout: '{"packages": [{"dependencies": ["abc"], "source": "hosted"}]}',
|
||||
),
|
||||
]);
|
||||
final PubDependenciesProjectValidator validator = PubDependenciesProjectValidator(processManager);
|
||||
|
||||
final List<ProjectValidatorResult> result = await validator.start(
|
||||
FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)
|
||||
);
|
||||
const String expected = 'All pub dependencies are hosted on https://pub.dartlang.org';
|
||||
expect(result.length, 1);
|
||||
expect(result[0].value, expected);
|
||||
expect(result[0].status, StatusProjectValidator.success);
|
||||
});
|
||||
|
||||
testWithoutContext('error when command dart pub deps fails', () async {
|
||||
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['dart', 'pub', 'deps', '--json'],
|
||||
stderr: 'command fail',
|
||||
),
|
||||
]);
|
||||
final PubDependenciesProjectValidator validator = PubDependenciesProjectValidator(processManager);
|
||||
|
||||
final List<ProjectValidatorResult> result = await validator.start(
|
||||
FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)
|
||||
);
|
||||
const String expected = 'command fail';
|
||||
expect(result.length, 1);
|
||||
expect(result[0].value, expected);
|
||||
expect(result[0].status, StatusProjectValidator.error);
|
||||
});
|
||||
|
||||
testWithoutContext('warning on dependencies not hosted', () async {
|
||||
final ProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['dart', 'pub', 'deps', '--json'],
|
||||
stdout: '{"packages": [{"dependencies": ["dep1", "dep2"], "source": "other"}]}',
|
||||
),
|
||||
]);
|
||||
final PubDependenciesProjectValidator validator = PubDependenciesProjectValidator(processManager);
|
||||
|
||||
final List<ProjectValidatorResult> result = await validator.start(
|
||||
FlutterProject.fromDirectoryTest(fileSystem.currentDirectory)
|
||||
);
|
||||
const String expected = 'dep1, dep2 are not hosted';
|
||||
expect(result.length, 1);
|
||||
expect(result[0].value, expected);
|
||||
expect(result[0].status, StatusProjectValidator.warning);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -54,5 +54,36 @@ void main() {
|
||||
|
||||
expect(loggerTest.statusText, contains(expected));
|
||||
});
|
||||
|
||||
testUsingContext('PubDependenciesProjectValidator success ', () async {
|
||||
final BufferLogger loggerTest = BufferLogger.test();
|
||||
final AnalyzeCommand command = AnalyzeCommand(
|
||||
artifacts: globals.artifacts!,
|
||||
fileSystem: fileSystem,
|
||||
logger: loggerTest,
|
||||
platform: globals.platform,
|
||||
terminal: globals.terminal,
|
||||
processManager: globals.processManager,
|
||||
allProjectValidators: <ProjectValidator>[
|
||||
PubDependenciesProjectValidator(globals.processManager),
|
||||
],
|
||||
);
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
|
||||
await runner.run(<String>[
|
||||
'analyze',
|
||||
'--no-pub',
|
||||
'--no-current-package',
|
||||
'--suggestions',
|
||||
'../../dev/integration_tests/flutter_gallery',
|
||||
]);
|
||||
|
||||
const String expected = '\n'
|
||||
'┌────────────────────────────────────────────────────────────────────────────────────┐\n'
|
||||
'│ Pub dependencies │\n'
|
||||
'│ [✓] Dart dependencies: All pub dependencies are hosted on https://pub.dartlang.org │\n'
|
||||
'└────────────────────────────────────────────────────────────────────────────────────┘\n';
|
||||
expect(loggerTest.statusText, contains(expected));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user