[flutter_tools] analyze --suggestions --machine command (#112217)
This commit is contained in:
parent
1a93ed3a6b
commit
21861423f2
@ -144,7 +144,14 @@ List<FlutterCommand> generateCommands({
|
||||
terminal: globals.terminal,
|
||||
artifacts: globals.artifacts!,
|
||||
// new ProjectValidators should be added here for the --suggestions to run
|
||||
allProjectValidators: <ProjectValidator>[GeneralInfoProjectValidator()],
|
||||
allProjectValidators: <ProjectValidator>[
|
||||
GeneralInfoProjectValidator(),
|
||||
VariableDumpMachineProjectValidator(
|
||||
logger: globals.logger,
|
||||
fileSystem: globals.fs,
|
||||
platform: globals.platform,
|
||||
),
|
||||
],
|
||||
),
|
||||
AssembleCommand(verboseHelp: verboseHelp, buildSystem: globals.buildSystem),
|
||||
AttachCommand(verboseHelp: verboseHelp),
|
||||
|
@ -66,6 +66,12 @@ class AnalyzeCommand extends FlutterCommand {
|
||||
argParser.addFlag('suggestions',
|
||||
help: 'Show suggestions about the current flutter project.'
|
||||
);
|
||||
argParser.addFlag('machine',
|
||||
negatable: false,
|
||||
help: 'Dumps a JSON with a subset of relevant data about the tool, project, '
|
||||
'and environment.',
|
||||
hide: !verboseHelp,
|
||||
);
|
||||
|
||||
// Hidden option to enable a benchmarking mode.
|
||||
argParser.addFlag('benchmark',
|
||||
@ -128,12 +134,18 @@ class AnalyzeCommand extends FlutterCommand {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Don't run pub if asking for machine output.
|
||||
if (boolArg('machine') != null && boolArg('machine')!) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return super.shouldRunPub;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
final bool? suggestionFlag = boolArg('suggestions');
|
||||
final bool machineFlag = boolArg('machine') ?? false;
|
||||
if (suggestionFlag != null && suggestionFlag == true) {
|
||||
final String directoryPath;
|
||||
final bool? watchFlag = boolArg('watch');
|
||||
@ -159,6 +171,7 @@ class AnalyzeCommand extends FlutterCommand {
|
||||
allProjectValidators: _allProjectValidators,
|
||||
userPath: directoryPath,
|
||||
processManager: _processManager,
|
||||
machine: machineFlag,
|
||||
).run();
|
||||
} else if (boolArgDeprecated('watch')) {
|
||||
await AnalyzeContinuously(
|
||||
|
@ -19,11 +19,13 @@ class ValidateProject {
|
||||
required this.userPath,
|
||||
required this.processManager,
|
||||
this.verbose = false,
|
||||
this.machine = false,
|
||||
});
|
||||
|
||||
final FileSystem fileSystem;
|
||||
final Logger logger;
|
||||
final bool verbose;
|
||||
final bool machine;
|
||||
final String userPath;
|
||||
final List<ProjectValidator> allProjectValidators;
|
||||
final ProcessManager processManager;
|
||||
@ -36,6 +38,9 @@ class ValidateProject {
|
||||
|
||||
bool hasCrash = false;
|
||||
for (final ProjectValidator validator in allProjectValidators) {
|
||||
if (validator.machineOutput != machine) {
|
||||
continue;
|
||||
}
|
||||
if (!results.containsKey(validator) && validator.supportsProject(project)) {
|
||||
results[validator] = validator.start(project).catchError((Object exception, StackTrace trace) {
|
||||
hasCrash = true;
|
||||
@ -45,15 +50,30 @@ class ValidateProject {
|
||||
}
|
||||
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
final List<String> resultsString = <String>[];
|
||||
for (final ProjectValidator validator in results.keys) {
|
||||
if (results[validator] != null) {
|
||||
resultsString.add(validator.title);
|
||||
addResultString(validator.title, await results[validator], resultsString);
|
||||
if (machine) {
|
||||
// Print properties
|
||||
buffer.write('{\n');
|
||||
for (final Future<List<ProjectValidatorResult>> resultListFuture in results.values) {
|
||||
final List<ProjectValidatorResult> resultList = await resultListFuture;
|
||||
int count = 0;
|
||||
for (final ProjectValidatorResult result in resultList) {
|
||||
count++;
|
||||
buffer.write(' "${result.name}": ${result.value}${count < resultList.length ? ',' : ''}\n');
|
||||
}
|
||||
}
|
||||
buffer.write('}');
|
||||
logger.printStatus(buffer.toString());
|
||||
} else {
|
||||
final List<String> resultsString = <String>[];
|
||||
for (final ProjectValidator validator in results.keys) {
|
||||
if (results[validator] != null) {
|
||||
resultsString.add(validator.title);
|
||||
addResultString(validator.title, await results[validator], resultsString);
|
||||
}
|
||||
}
|
||||
buffer.writeAll(resultsString, '\n');
|
||||
logger.printBox(buffer.toString());
|
||||
}
|
||||
buffer.writeAll(resultsString, '\n');
|
||||
logger.printBox(buffer.toString());
|
||||
|
||||
if (hasCrash) {
|
||||
return const FlutterCommandResult(ExitStatus.fail);
|
||||
|
@ -6,21 +6,199 @@ import 'dart:collection';
|
||||
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import 'base/file_system.dart';
|
||||
import 'base/io.dart';
|
||||
import 'base/logger.dart';
|
||||
import 'base/platform.dart';
|
||||
import 'cache.dart';
|
||||
import 'convert.dart';
|
||||
import 'dart_pub_json_formatter.dart';
|
||||
import 'flutter_manifest.dart';
|
||||
import 'project.dart';
|
||||
import 'project_validator_result.dart';
|
||||
import 'version.dart';
|
||||
|
||||
abstract class ProjectValidator {
|
||||
const ProjectValidator();
|
||||
String get title;
|
||||
bool get machineOutput => false;
|
||||
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);
|
||||
}
|
||||
|
||||
abstract class MachineProjectValidator extends ProjectValidator {
|
||||
const MachineProjectValidator();
|
||||
|
||||
@override
|
||||
bool get machineOutput => true;
|
||||
}
|
||||
|
||||
/// Validator run for all platforms that extract information from the pubspec.yaml.
|
||||
///
|
||||
/// Specific info from different platforms should be written in their own ProjectValidator.
|
||||
class VariableDumpMachineProjectValidator extends MachineProjectValidator {
|
||||
VariableDumpMachineProjectValidator({
|
||||
required this.logger,
|
||||
required this.fileSystem,
|
||||
required this.platform,
|
||||
});
|
||||
|
||||
final Logger logger;
|
||||
final FileSystem fileSystem;
|
||||
final Platform platform;
|
||||
|
||||
String _toJsonValue(Object? obj) {
|
||||
String value = obj.toString();
|
||||
if (obj is String) {
|
||||
value = '"$obj"';
|
||||
}
|
||||
value = value.replaceAll(r'\', r'\\');
|
||||
return value;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<ProjectValidatorResult>> start(FlutterProject project) async {
|
||||
final List<ProjectValidatorResult> result = <ProjectValidatorResult>[];
|
||||
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.directory',
|
||||
value: _toJsonValue(project.directory.absolute.path),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.metadataFile',
|
||||
value: _toJsonValue(project.metadataFile.absolute.path),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.android.exists',
|
||||
value: _toJsonValue(project.android.existsSync()),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.ios.exists',
|
||||
value: _toJsonValue(project.ios.exists),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.web.exists',
|
||||
value: _toJsonValue(project.web.existsSync()),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.macos.exists',
|
||||
value: _toJsonValue(project.macos.existsSync()),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.linux.exists',
|
||||
value: _toJsonValue(project.linux.existsSync()),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.windows.exists',
|
||||
value: _toJsonValue(project.windows.existsSync()),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.fuchsia.exists',
|
||||
value: _toJsonValue(project.fuchsia.existsSync()),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.android.isKotlin',
|
||||
value: _toJsonValue(project.android.isKotlin),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.ios.isSwift',
|
||||
value: _toJsonValue(project.ios.isSwift),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.isModule',
|
||||
value: _toJsonValue(project.isModule),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.isPlugin',
|
||||
value: _toJsonValue(project.isPlugin),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterProject.manifest.appname',
|
||||
value: _toJsonValue(project.manifest.appName),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
|
||||
// FlutterVersion
|
||||
final FlutterVersion version = FlutterVersion(workingDirectory: project.directory.absolute.path);
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'FlutterVersion.frameworkRevision',
|
||||
value: _toJsonValue(version.frameworkRevision),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
|
||||
// Platform
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'Platform.operatingSystem',
|
||||
value: _toJsonValue(platform.operatingSystem),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'Platform.isAndroid',
|
||||
value: _toJsonValue(platform.isAndroid),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'Platform.isIOS',
|
||||
value: _toJsonValue(platform.isIOS),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'Platform.isWindows',
|
||||
value: _toJsonValue(platform.isWindows),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'Platform.isMacOS',
|
||||
value: _toJsonValue(platform.isMacOS),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'Platform.isFuchsia',
|
||||
value: _toJsonValue(platform.isFuchsia),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'Platform.pathSeparator',
|
||||
value: _toJsonValue(platform.pathSeparator),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
|
||||
// Cache
|
||||
result.add(ProjectValidatorResult(
|
||||
name: 'Cache.flutterRoot',
|
||||
value: _toJsonValue(Cache.flutterRoot),
|
||||
status: StatusProjectValidator.info,
|
||||
));
|
||||
return result;
|
||||
}
|
||||
|
||||
@override
|
||||
bool supportsProject(FlutterProject project) {
|
||||
// this validator will run for any type of project
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
String get title => 'Machine JSON variable dump';
|
||||
}
|
||||
|
||||
/// Validator run for all platforms that extract information from the pubspec.yaml.
|
||||
///
|
||||
/// Specific info from different platforms should be written in their own ProjectValidator.
|
||||
|
@ -277,9 +277,8 @@ class FlutterCommandRunner extends CommandRunner<void> {
|
||||
globals.printStatus(status);
|
||||
return;
|
||||
}
|
||||
|
||||
if (machineFlag) {
|
||||
throwToolExit('The "--machine" flag is only valid with the "--version" flag.', exitCode: 2);
|
||||
if (machineFlag && topLevelResults.command?.name != 'analyze') {
|
||||
throwToolExit('The "--machine" flag is only valid with the "--version" flag or the "analzye --suggestions" command.', exitCode: 2);
|
||||
}
|
||||
await super.runCommand(topLevelResults);
|
||||
},
|
||||
|
@ -97,7 +97,7 @@ void main() {
|
||||
allProjectValidators: <ProjectValidator>[
|
||||
ProjectValidatorDummy(),
|
||||
ProjectValidatorSecondDummy()
|
||||
]
|
||||
],
|
||||
);
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
|
||||
@ -128,7 +128,7 @@ void main() {
|
||||
processManager: processManager,
|
||||
allProjectValidators: <ProjectValidator>[
|
||||
ProjectValidatorCrash(),
|
||||
]
|
||||
],
|
||||
);
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
|
||||
@ -148,7 +148,7 @@ void main() {
|
||||
platform: platform,
|
||||
terminal: terminal,
|
||||
processManager: processManager,
|
||||
allProjectValidators: <ProjectValidator>[]
|
||||
allProjectValidators: <ProjectValidator>[],
|
||||
);
|
||||
final CommandRunner<void> runner = createTestCommandRunner(command);
|
||||
Future<void> result () => runner.run(<String>['analyze', '--suggestions', '--watch']);
|
||||
|
@ -25,7 +25,7 @@ void main() {
|
||||
for (final Command<void> command in runner.commands.values) {
|
||||
if(command.name == 'analyze') {
|
||||
final AnalyzeCommand analyze = command as AnalyzeCommand;
|
||||
expect(analyze.allProjectValidators().length, 1);
|
||||
expect(analyze.allProjectValidators().length, 2);
|
||||
}
|
||||
}
|
||||
}));
|
||||
|
@ -2,15 +2,21 @@
|
||||
// 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:args/command_runner.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/io.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/commands/analyze.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/project_validator.dart';
|
||||
|
||||
import '../src/common.dart';
|
||||
import '../src/context.dart';
|
||||
import '../src/test_flutter_command_runner.dart';
|
||||
import 'test_utils.dart';
|
||||
|
||||
void main() {
|
||||
late FileSystem fileSystem;
|
||||
@ -86,4 +92,76 @@ void main() {
|
||||
expect(loggerTest.statusText, contains(expected));
|
||||
});
|
||||
});
|
||||
|
||||
group('analyze --suggestions --machine command integration', () {
|
||||
late Directory tempDir;
|
||||
late Platform platform;
|
||||
|
||||
setUpAll(() async {
|
||||
platform = const LocalPlatform();
|
||||
tempDir = createResolvedTempDirectorySync('run_test.');
|
||||
await globals.processManager.run(<String>['flutter', 'create', 'test_project'], workingDirectory: tempDir.path);
|
||||
});
|
||||
|
||||
tearDown(() async {
|
||||
tryToDelete(tempDir);
|
||||
});
|
||||
|
||||
testUsingContext('analyze --suggesions --machine produces expected values', () async {
|
||||
final ProcessResult result = await globals.processManager.run(<String>['flutter', 'analyze', '--suggestions', '--machine'], workingDirectory: tempDir.childDirectory('test_project').path);
|
||||
|
||||
expect(result.stdout is String, true);
|
||||
expect((result.stdout as String).startsWith('{\n'), true);
|
||||
expect(result.stdout, isNot(contains(',\n}'))); // No trailing commas allowed in JSON
|
||||
expect((result.stdout as String).endsWith('}\n'), true);
|
||||
|
||||
final Map<String, dynamic> decoded = jsonDecode(result.stdout as String) as Map<String, dynamic>;
|
||||
|
||||
expect(decoded.containsKey('FlutterProject.android.exists'), true);
|
||||
expect(decoded.containsKey('FlutterProject.ios.exists'), true);
|
||||
expect(decoded.containsKey('FlutterProject.web.exists'), true);
|
||||
expect(decoded.containsKey('FlutterProject.macos.exists'), true);
|
||||
expect(decoded.containsKey('FlutterProject.linux.exists'), true);
|
||||
expect(decoded.containsKey('FlutterProject.windows.exists'), true);
|
||||
expect(decoded.containsKey('FlutterProject.fuchsia.exists'), true);
|
||||
expect(decoded.containsKey('FlutterProject.android.isKotlin'), true);
|
||||
expect(decoded.containsKey('FlutterProject.ios.isSwift'), true);
|
||||
expect(decoded.containsKey('FlutterProject.isModule'), true);
|
||||
expect(decoded.containsKey('FlutterProject.isPlugin'), true);
|
||||
expect(decoded.containsKey('FlutterProject.manifest.appname'), true);
|
||||
expect(decoded.containsKey('FlutterVersion.frameworkRevision'), true);
|
||||
|
||||
expect(decoded.containsKey('FlutterProject.directory'), true);
|
||||
expect(decoded.containsKey('FlutterProject.metadataFile'), true);
|
||||
expect(decoded.containsKey('Platform.operatingSystem'), true);
|
||||
expect(decoded.containsKey('Platform.isAndroid'), true);
|
||||
expect(decoded.containsKey('Platform.isIOS'), true);
|
||||
expect(decoded.containsKey('Platform.isWindows'), true);
|
||||
expect(decoded.containsKey('Platform.isMacOS'), true);
|
||||
expect(decoded.containsKey('Platform.isFuchsia'), true);
|
||||
expect(decoded.containsKey('Platform.pathSeparator'), true);
|
||||
expect(decoded.containsKey('Cache.flutterRoot'), true);
|
||||
|
||||
expect(decoded['FlutterProject.android.exists'], true);
|
||||
expect(decoded['FlutterProject.ios.exists'], true);
|
||||
expect(decoded['FlutterProject.web.exists'], true);
|
||||
expect(decoded['FlutterProject.macos.exists'], true);
|
||||
expect(decoded['FlutterProject.linux.exists'], true);
|
||||
expect(decoded['FlutterProject.windows.exists'], true);
|
||||
expect(decoded['FlutterProject.fuchsia.exists'], false);
|
||||
expect(decoded['FlutterProject.android.isKotlin'], true);
|
||||
expect(decoded['FlutterProject.ios.isSwift'], true);
|
||||
expect(decoded['FlutterProject.isModule'], false);
|
||||
expect(decoded['FlutterProject.isPlugin'], false);
|
||||
expect(decoded['FlutterProject.manifest.appname'], 'test_project');
|
||||
expect(decoded['FlutterVersion.frameworkRevision'], '');
|
||||
|
||||
expect(decoded['Platform.isAndroid'], false);
|
||||
expect(decoded['Platform.isIOS'], false);
|
||||
expect(decoded['Platform.isWindows'], platform.isWindows);
|
||||
expect(decoded['Platform.isMacOS'], platform.isMacOS);
|
||||
expect(decoded['Platform.isFuchsia'], platform.isFuchsia);
|
||||
expect(decoded['Platform.pathSeparator'], platform.pathSeparator);
|
||||
}, overrides: <Type, Generator>{});
|
||||
});
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user