Fix and update version checks (#3792)

Currently our version update checks aren't actually working; the script doesn't work correctly if no explicit --base-sha is passed, but that's always how CI is calling it.

Fixes https://github.com/flutter/flutter/issues/79823 (and version checks in general)

This makes a number of changes:
- Fixes it to work without --base-sha
- Adds tests that it works in that mode
  - And tightens existing tests to require ToolExit, not just any error, to reduce false-positive test success
- Adds verbose logging of the checks being done, to make it easier to debug this kind of issue in the future
- Tightens the exception handling for missing previous versions to just the line that's expected to fail in that case
- Only allows missing versions when "publish_to: none" is set
  - Adds that everywhere it's missing
  - Standardize the format in the repo to "none" (instead of also having "'none'").
- Allows the use of NEXT in CHANGELOG as a way of gathering changes that are worth noting, but not
  doing a publish cycle for. (Replaces the plan of using -dev versions, since that's actually harder to implement,
  and more confusing.)
  - Ensures that we don't forget to clean up NEXT entries when bumping versions
This commit is contained in:
stuartmorgan
2021-04-08 13:22:19 -07:00
committed by GitHub
parent 3d9e523218
commit b2eefc9158
4 changed files with 287 additions and 77 deletions

View File

@ -13,8 +13,6 @@ import 'package:pubspec_parse/pubspec_parse.dart';
import 'common.dart';
const String _kBaseSha = 'base-sha';
/// Categories of version change types.
enum NextVersionType {
/// A breaking change.
@ -96,48 +94,59 @@ class VersionCheckCommand extends PluginCommand {
final List<String> changedPubspecs =
await gitVersionFinder.getChangedPubSpecs();
final String baseSha = argResults[_kBaseSha] as String;
const String indentation = ' ';
for (final String pubspecPath in changedPubspecs) {
try {
final File pubspecFile = fileSystem.file(pubspecPath);
if (!pubspecFile.existsSync()) {
continue;
}
final Pubspec pubspec = Pubspec.parse(pubspecFile.readAsStringSync());
if (pubspec.publishTo == 'none') {
continue;
}
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 masterVersion =
await gitVersionFinder.getPackageVersion(pubspecPath, baseSha);
final Version headVersion =
await gitVersionFinder.getPackageVersion(pubspecPath, 'HEAD');
if (headVersion == null) {
continue; // Example apps don't have versions
}
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".');
}
final Version masterVersion =
await gitVersionFinder.getPackageVersion(pubspecPath);
if (masterVersion == null) {
print('${indentation}Unable to find pubspec in master. '
'Safe to ignore if the project is new.');
}
final Map<Version, NextVersionType> allowedNextVersions =
getAllowedNextVersions(masterVersion, headVersion);
if (masterVersion == headVersion) {
print('${indentation}No version change.');
continue;
}
if (!allowedNextVersions.containsKey(headVersion)) {
final String error = '$pubspecPath incorrectly updated version.\n'
'HEAD: $headVersion, master: $masterVersion.\n'
'Allowed versions: $allowedNextVersions';
printErrorAndExit(errorMessage: error);
}
final Map<Version, NextVersionType> allowedNextVersions =
getAllowedNextVersions(masterVersion, headVersion);
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);
}
} on io.ProcessException {
print('Unable to find pubspec in master for $pubspecPath.'
' Safe to ignore if the project is new.');
if (!allowedNextVersions.containsKey(headVersion)) {
final String error = '${indentation}Incorrectly updated version.\n'
'${indentation}HEAD: $headVersion, master: $masterVersion.\n'
'${indentation}Allowed versions: $allowedNextVersions';
printErrorAndExit(errorMessage: error);
} else {
print('$indentation$headVersion -> $masterVersion');
}
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);
}
}
@ -153,7 +162,7 @@ class VersionCheckCommand extends PluginCommand {
final String packageName = plugin.basename;
print('-----------------------------------------');
print(
'Checking the first version listed in CHANGELOG.MD matches the version in pubspec.yaml for $packageName.');
'Checking the first version listed in CHANGELOG.md matches the version in pubspec.yaml for $packageName.');
final Pubspec pubspec = _tryParsePubspec(plugin);
if (pubspec == null) {
@ -169,12 +178,29 @@ class VersionCheckCommand extends PluginCommand {
final Iterator<String> iterator = lines.iterator;
while (iterator.moveNext()) {
if (iterator.current.trim().isNotEmpty) {
firstLineWithText = iterator.current;
firstLineWithText = iterator.current.trim();
break;
}
}
// Remove all leading mark down syntax from the version line.
final String versionString = firstLineWithText.split(' ').last;
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.
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 =
@ -190,6 +216,18 @@ 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');
}