mirror of
https://github.com/flutter/packages.git
synced 2025-08-06 17:28:42 +08:00

The purpose of this PR is to make running all unit tests on Windows pass (vs failing a large portion of the tests as currently happens). This does not mean that the commands actually work when run on Windows, or that Windows support is tested, only that it's possible to actually run the tests themselves. This is prep for actually supporting parts of the tool on Windows in future PRs. Major changes: - Make the tests significantly more hermetic: - Make almost all tools take a `Platform` constructor argument that can be used to inject a mock platform to control what OS the command acts like it is running on under test. - Add a path `Context` object to the base command, whose style matches the `Platform`, and use that almost everywhere instead of the top-level `path` functions. - In cases where Posix behavior is always required (such as parsing `git` output), explicitly use the `posix` context object for `path` functions. - Start laying the groundwork for actual Windows support: - Replace all uses of `flutter` as a command with a getter that returns `flutter` or `flutter.bat` as appropriate. - For user messages that include relative paths, use a helper that always uses Posix-style relative paths for consistent output. This bumps the version since quite a few changes have built up, and having a cut point before starting to make more changes to the commands to support Windows seems like a good idea. Part of https://github.com/flutter/flutter/issues/86113
172 lines
5.1 KiB
Dart
172 lines
5.1 KiB
Dart
// Copyright 2013 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/file.dart';
|
|
import 'package:git/git.dart';
|
|
import 'package:platform/platform.dart';
|
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
|
|
|
import 'common/package_looping_command.dart';
|
|
import 'common/process_runner.dart';
|
|
|
|
/// A command to enforce pubspec conventions across the repository.
|
|
///
|
|
/// This both ensures that repo best practices for which optional fields are
|
|
/// used are followed, and that the structure is consistent to make edits
|
|
/// across multiple pubspec files easier.
|
|
class PubspecCheckCommand extends PackageLoopingCommand {
|
|
/// Creates an instance of the version check command.
|
|
PubspecCheckCommand(
|
|
Directory packagesDir, {
|
|
ProcessRunner processRunner = const ProcessRunner(),
|
|
Platform platform = const LocalPlatform(),
|
|
GitDir? gitDir,
|
|
}) : super(
|
|
packagesDir,
|
|
processRunner: processRunner,
|
|
platform: platform,
|
|
gitDir: gitDir,
|
|
);
|
|
|
|
// Section order for plugins. Because the 'flutter' section is critical
|
|
// information for plugins, and usually small, it goes near the top unlike in
|
|
// a normal app or package.
|
|
static const List<String> _majorPluginSections = <String>[
|
|
'environment:',
|
|
'flutter:',
|
|
'dependencies:',
|
|
'dev_dependencies:',
|
|
];
|
|
|
|
static const List<String> _majorPackageSections = <String>[
|
|
'environment:',
|
|
'dependencies:',
|
|
'dev_dependencies:',
|
|
'flutter:',
|
|
];
|
|
|
|
static const String _expectedIssueLinkFormat =
|
|
'https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A';
|
|
|
|
@override
|
|
final String name = 'pubspec-check';
|
|
|
|
@override
|
|
final String description =
|
|
'Checks that pubspecs follow repository conventions.';
|
|
|
|
@override
|
|
bool get hasLongOutput => false;
|
|
|
|
@override
|
|
bool get includeSubpackages => true;
|
|
|
|
@override
|
|
Future<PackageResult> runForPackage(Directory package) async {
|
|
final File pubspec = package.childFile('pubspec.yaml');
|
|
final bool passesCheck = !pubspec.existsSync() ||
|
|
await _checkPubspec(pubspec, packageName: package.basename);
|
|
if (!passesCheck) {
|
|
return PackageResult.fail();
|
|
}
|
|
return PackageResult.success();
|
|
}
|
|
|
|
Future<bool> _checkPubspec(
|
|
File pubspecFile, {
|
|
required String packageName,
|
|
}) async {
|
|
final String contents = pubspecFile.readAsStringSync();
|
|
final Pubspec? pubspec = _tryParsePubspec(contents);
|
|
if (pubspec == null) {
|
|
return false;
|
|
}
|
|
|
|
final List<String> pubspecLines = contents.split('\n');
|
|
final List<String> sectionOrder = pubspecLines.contains(' plugin:')
|
|
? _majorPluginSections
|
|
: _majorPackageSections;
|
|
bool passing = _checkSectionOrder(pubspecLines, sectionOrder);
|
|
if (!passing) {
|
|
print('${indentation}Major sections should follow standard '
|
|
'repository ordering:');
|
|
final String listIndentation = indentation * 2;
|
|
print('$listIndentation${sectionOrder.join('\n$listIndentation')}');
|
|
}
|
|
|
|
if (pubspec.publishTo != 'none') {
|
|
final List<String> repositoryErrors =
|
|
_checkForRepositoryLinkErrors(pubspec, packageName: packageName);
|
|
if (repositoryErrors.isNotEmpty) {
|
|
for (final String error in repositoryErrors) {
|
|
print('$indentation$error');
|
|
}
|
|
passing = false;
|
|
}
|
|
|
|
if (!_checkIssueLink(pubspec)) {
|
|
print(
|
|
'${indentation}A package should have an "issue_tracker" link to a '
|
|
'search for open flutter/flutter bugs with the relevant label:\n'
|
|
'${indentation * 2}$_expectedIssueLinkFormat<package label>');
|
|
passing = false;
|
|
}
|
|
}
|
|
|
|
return passing;
|
|
}
|
|
|
|
Pubspec? _tryParsePubspec(String pubspecContents) {
|
|
try {
|
|
return Pubspec.parse(pubspecContents);
|
|
} on Exception catch (exception) {
|
|
print(' Cannot parse pubspec.yaml: $exception');
|
|
}
|
|
return null;
|
|
}
|
|
|
|
bool _checkSectionOrder(
|
|
List<String> pubspecLines, List<String> sectionOrder) {
|
|
int previousSectionIndex = 0;
|
|
for (final String line in pubspecLines) {
|
|
final int index = sectionOrder.indexOf(line);
|
|
if (index == -1) {
|
|
continue;
|
|
}
|
|
if (index < previousSectionIndex) {
|
|
return false;
|
|
}
|
|
previousSectionIndex = index;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
List<String> _checkForRepositoryLinkErrors(
|
|
Pubspec pubspec, {
|
|
required String packageName,
|
|
}) {
|
|
final List<String> errorMessages = <String>[];
|
|
if (pubspec.repository == null) {
|
|
errorMessages.add('Missing "repository"');
|
|
} else if (!pubspec.repository!.path.endsWith(packageName)) {
|
|
errorMessages
|
|
.add('The "repository" link should end with the package name.');
|
|
}
|
|
|
|
if (pubspec.homepage != null) {
|
|
errorMessages
|
|
.add('Found a "homepage" entry; only "repository" should be used.');
|
|
}
|
|
|
|
return errorMessages;
|
|
}
|
|
|
|
bool _checkIssueLink(Pubspec pubspec) {
|
|
return pubspec.issueTracker
|
|
?.toString()
|
|
.startsWith(_expectedIssueLinkFormat) ==
|
|
true;
|
|
}
|
|
}
|