mirror of
https://github.com/flutter/packages.git
synced 2025-06-04 18:41:02 +08:00

- Updates dependencies to null-safe versions - Migrates common.dart (which doesn't depend on anything) - Migrates common_tests.dart and its one dependency, utils.dart - Adds build_runner for Mockito mock generation - Adds a new utility methods for getting arguments that handle both the casting and the removal of nullability to address a common problematic pattern while migrating code. - Converts all files, not just the migrated ones, to those new helpers. Migrating common.dart and utils.dart should unblock a command-by-command migration to null safety. Reverts the separate of podspect lints into a step that doesn't do a Flutter upgrade (https://github.com/flutter/plugins/pull/3700) because without that step we had a version of Dart too old to run null-safe tooling. First step of https://github.com/flutter/flutter/issues/81912
306 lines
11 KiB
Dart
306 lines
11 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.
|
|
|
|
// @dart=2.9
|
|
|
|
import 'dart:async';
|
|
|
|
import 'package:file/file.dart';
|
|
import 'package:git/git.dart';
|
|
import 'package:http/http.dart' as http;
|
|
import 'package:meta/meta.dart';
|
|
import 'package:pub_semver/pub_semver.dart';
|
|
import 'package:pubspec_parse/pubspec_parse.dart';
|
|
|
|
import 'common.dart';
|
|
|
|
/// Categories of version change types.
|
|
enum NextVersionType {
|
|
/// A breaking change.
|
|
BREAKING_MAJOR,
|
|
|
|
/// A minor change (e.g., added feature).
|
|
MINOR,
|
|
|
|
/// A bugfix change.
|
|
PATCH,
|
|
|
|
/// The release of an existing prerelease version.
|
|
RELEASE,
|
|
}
|
|
|
|
/// Returns the set of allowed next versions, with their change type, for
|
|
/// [masterVersion].
|
|
///
|
|
/// [headVerison] is used to check whether this is a pre-1.0 version bump, as
|
|
/// those have different semver rules.
|
|
@visibleForTesting
|
|
Map<Version, NextVersionType> getAllowedNextVersions(
|
|
Version masterVersion, Version headVersion) {
|
|
final Map<Version, NextVersionType> allowedNextVersions =
|
|
<Version, NextVersionType>{
|
|
masterVersion.nextMajor: NextVersionType.BREAKING_MAJOR,
|
|
masterVersion.nextMinor: NextVersionType.MINOR,
|
|
masterVersion.nextPatch: NextVersionType.PATCH,
|
|
};
|
|
|
|
if (masterVersion.major < 1 && headVersion.major < 1) {
|
|
int nextBuildNumber = -1;
|
|
if (masterVersion.build.isEmpty) {
|
|
nextBuildNumber = 1;
|
|
} else {
|
|
final int currentBuildNumber = masterVersion.build.first as int;
|
|
nextBuildNumber = currentBuildNumber + 1;
|
|
}
|
|
final Version preReleaseVersion = Version(
|
|
masterVersion.major,
|
|
masterVersion.minor,
|
|
masterVersion.patch,
|
|
build: nextBuildNumber.toString(),
|
|
);
|
|
allowedNextVersions.clear();
|
|
allowedNextVersions[masterVersion.nextMajor] = NextVersionType.RELEASE;
|
|
allowedNextVersions[masterVersion.nextMinor] =
|
|
NextVersionType.BREAKING_MAJOR;
|
|
allowedNextVersions[masterVersion.nextPatch] = NextVersionType.MINOR;
|
|
allowedNextVersions[preReleaseVersion] = NextVersionType.PATCH;
|
|
}
|
|
return allowedNextVersions;
|
|
}
|
|
|
|
/// A command to validate version changes to packages.
|
|
class VersionCheckCommand extends PluginCommand {
|
|
/// Creates an instance of the version check command.
|
|
VersionCheckCommand(
|
|
Directory packagesDir,
|
|
FileSystem fileSystem, {
|
|
ProcessRunner processRunner = const ProcessRunner(),
|
|
GitDir gitDir,
|
|
this.httpClient,
|
|
}) : _pubVersionFinder =
|
|
PubVersionFinder(httpClient: httpClient ?? http.Client()),
|
|
super(packagesDir, fileSystem,
|
|
processRunner: processRunner, gitDir: gitDir) {
|
|
argParser.addFlag(
|
|
_againstPubFlag,
|
|
help: 'Whether the version check should run against the version on pub.\n'
|
|
'Defaults to false, which means the version check only run against the previous version in code.',
|
|
defaultsTo: false,
|
|
negatable: true,
|
|
);
|
|
}
|
|
|
|
static const String _againstPubFlag = 'against-pub';
|
|
|
|
@override
|
|
final String name = 'version-check';
|
|
|
|
@override
|
|
final String description =
|
|
'Checks if the versions of the plugins have been incremented per pub specification.\n'
|
|
'Also checks if the latest version in CHANGELOG matches the version in pubspec.\n\n'
|
|
'This command requires "pub" and "flutter" to be in your path.';
|
|
|
|
/// The http client used to query pub server.
|
|
final http.Client httpClient;
|
|
|
|
final PubVersionFinder _pubVersionFinder;
|
|
|
|
@override
|
|
Future<void> run() async {
|
|
final GitVersionFinder gitVersionFinder = await retrieveVersionFinder();
|
|
|
|
final List<String> changedPubspecs =
|
|
await gitVersionFinder.getChangedPubSpecs();
|
|
|
|
const String indentation = ' ';
|
|
for (final String pubspecPath in changedPubspecs) {
|
|
print('Checking versions for $pubspecPath...');
|
|
final File pubspecFile = fileSystem.file(pubspecPath);
|
|
if (!pubspecFile.existsSync()) {
|
|
print('${indentation}Deleted; skipping.');
|
|
continue;
|
|
}
|
|
final Pubspec pubspec = Pubspec.parse(pubspecFile.readAsStringSync());
|
|
if (pubspec.publishTo == 'none') {
|
|
print('${indentation}Found "publish_to: none"; skipping.');
|
|
continue;
|
|
}
|
|
|
|
final Version headVersion =
|
|
await gitVersionFinder.getPackageVersion(pubspecPath, gitRef: 'HEAD');
|
|
if (headVersion == null) {
|
|
printErrorAndExit(
|
|
errorMessage: '${indentation}No version found. A package that '
|
|
'intentionally has no version should be marked '
|
|
'"publish_to: none".');
|
|
}
|
|
Version sourceVersion;
|
|
if (getBoolArg(_againstPubFlag)) {
|
|
final String packageName = pubspecFile.parent.basename;
|
|
final PubVersionFinderResponse pubVersionFinderResponse =
|
|
await _pubVersionFinder.getPackageVersion(package: packageName);
|
|
switch (pubVersionFinderResponse.result) {
|
|
case PubVersionFinderResult.success:
|
|
sourceVersion = pubVersionFinderResponse.versions.first;
|
|
print(
|
|
'$indentation$packageName: Current largest version on pub: $sourceVersion');
|
|
break;
|
|
case PubVersionFinderResult.fail:
|
|
printErrorAndExit(errorMessage: '''
|
|
${indentation}Error fetching version on pub for $packageName.
|
|
${indentation}HTTP Status ${pubVersionFinderResponse.httpResponse.statusCode}
|
|
${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
|
|
''');
|
|
break;
|
|
case PubVersionFinderResult.noPackageFound:
|
|
sourceVersion = null;
|
|
break;
|
|
}
|
|
} else {
|
|
sourceVersion = await gitVersionFinder.getPackageVersion(pubspecPath);
|
|
}
|
|
if (sourceVersion == null) {
|
|
String safeToIgnoreMessage;
|
|
if (getBoolArg(_againstPubFlag)) {
|
|
safeToIgnoreMessage =
|
|
'${indentation}Unable to find package on pub server.';
|
|
} else {
|
|
safeToIgnoreMessage =
|
|
'${indentation}Unable to find pubspec in master.';
|
|
}
|
|
print('$safeToIgnoreMessage Safe to ignore if the project is new.');
|
|
continue;
|
|
}
|
|
|
|
if (sourceVersion == headVersion) {
|
|
print('${indentation}No version change.');
|
|
continue;
|
|
}
|
|
|
|
final Map<Version, NextVersionType> allowedNextVersions =
|
|
getAllowedNextVersions(sourceVersion, headVersion);
|
|
|
|
if (!allowedNextVersions.containsKey(headVersion)) {
|
|
final String source = (getBoolArg(_againstPubFlag)) ? 'pub' : 'master';
|
|
final String error = '${indentation}Incorrectly updated version.\n'
|
|
'${indentation}HEAD: $headVersion, $source: $sourceVersion.\n'
|
|
'${indentation}Allowed versions: $allowedNextVersions';
|
|
printErrorAndExit(errorMessage: error);
|
|
} else {
|
|
print('$indentation$headVersion -> $sourceVersion');
|
|
}
|
|
|
|
final bool isPlatformInterface =
|
|
pubspec.name.endsWith('_platform_interface');
|
|
if (isPlatformInterface &&
|
|
allowedNextVersions[headVersion] == NextVersionType.BREAKING_MAJOR) {
|
|
final String error = '$pubspecPath breaking change detected.\n'
|
|
'Breaking changes to platform interfaces are strongly discouraged.\n';
|
|
printErrorAndExit(errorMessage: error);
|
|
}
|
|
}
|
|
|
|
await for (final Directory plugin in getPlugins()) {
|
|
await _checkVersionsMatch(plugin);
|
|
}
|
|
_pubVersionFinder.httpClient.close();
|
|
|
|
print('No version check errors found!');
|
|
}
|
|
|
|
Future<void> _checkVersionsMatch(Directory plugin) async {
|
|
// get version from pubspec
|
|
final String packageName = plugin.basename;
|
|
print('-----------------------------------------');
|
|
print(
|
|
'Checking the first version listed in CHANGELOG.md matches the version in pubspec.yaml for $packageName.');
|
|
|
|
final Pubspec pubspec = _tryParsePubspec(plugin);
|
|
if (pubspec == null) {
|
|
const String error = 'Cannot parse version from pubspec.yaml';
|
|
printErrorAndExit(errorMessage: error);
|
|
}
|
|
final Version fromPubspec = pubspec.version;
|
|
|
|
// get first version from CHANGELOG
|
|
final File changelog = plugin.childFile('CHANGELOG.md');
|
|
final List<String> lines = changelog.readAsLinesSync();
|
|
String firstLineWithText;
|
|
final Iterator<String> iterator = lines.iterator;
|
|
while (iterator.moveNext()) {
|
|
if (iterator.current.trim().isNotEmpty) {
|
|
firstLineWithText = iterator.current.trim();
|
|
break;
|
|
}
|
|
}
|
|
// Remove all leading mark down syntax from the version line.
|
|
String versionString = firstLineWithText.split(' ').last;
|
|
|
|
// Skip validation for the special NEXT version that's used to accumulate
|
|
// changes that don't warrant publishing on their own.
|
|
final bool hasNextSection = versionString == 'NEXT';
|
|
if (hasNextSection) {
|
|
print('Found NEXT; validating next version in the CHANGELOG.');
|
|
// Ensure that the version in pubspec hasn't changed without updating
|
|
// CHANGELOG. That means the next version entry in the CHANGELOG pass the
|
|
// normal validation.
|
|
while (iterator.moveNext()) {
|
|
if (iterator.current.trim().startsWith('## ')) {
|
|
versionString = iterator.current.trim().split(' ').last;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
final Version fromChangeLog = Version.parse(versionString);
|
|
if (fromChangeLog == null) {
|
|
final String error =
|
|
'Cannot find version on the first line of ${plugin.path}/CHANGELOG.md';
|
|
printErrorAndExit(errorMessage: error);
|
|
}
|
|
|
|
if (fromPubspec != fromChangeLog) {
|
|
final String error = '''
|
|
versions for $packageName in CHANGELOG.md and pubspec.yaml do not match.
|
|
The version in pubspec.yaml is $fromPubspec.
|
|
The first version listed in CHANGELOG.md is $fromChangeLog.
|
|
''';
|
|
printErrorAndExit(errorMessage: error);
|
|
}
|
|
|
|
// If NEXT wasn't the first section, it should not exist at all.
|
|
if (!hasNextSection) {
|
|
final RegExp nextRegex = RegExp(r'^#+\s*NEXT\s*$');
|
|
if (lines.any((String line) => nextRegex.hasMatch(line))) {
|
|
printErrorAndExit(errorMessage: '''
|
|
When bumping the version for release, the NEXT section should be incorporated
|
|
into the new version's release notes.
|
|
''');
|
|
}
|
|
}
|
|
|
|
print('$packageName passed version check');
|
|
}
|
|
|
|
Pubspec _tryParsePubspec(Directory package) {
|
|
final File pubspecFile = package.childFile('pubspec.yaml');
|
|
|
|
try {
|
|
final Pubspec pubspec = Pubspec.parse(pubspecFile.readAsStringSync());
|
|
if (pubspec == null) {
|
|
final String error =
|
|
'Failed to parse `pubspec.yaml` at ${pubspecFile.path}';
|
|
printErrorAndExit(errorMessage: error);
|
|
}
|
|
return pubspec;
|
|
} on Exception catch (exception) {
|
|
final String error =
|
|
'Failed to parse `pubspec.yaml` at ${pubspecFile.path}: $exception}';
|
|
printErrorAndExit(errorMessage: error);
|
|
}
|
|
return null;
|
|
}
|
|
}
|