Skip to content
This repository has been archived by the owner on Feb 22, 2023. It is now read-only.

[tools] Allow pre-release versions #6061

Merged
merged 2 commits into from Jul 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
4 changes: 4 additions & 0 deletions script/tool/CHANGELOG.md
@@ -1,3 +1,7 @@
## 0.8.8

- Allows pre-release versions in `version-check`.

## 0.8.7

- Supports empty custom analysis allow list files.
Expand Down
51 changes: 39 additions & 12 deletions script/tool/lib/src/version_check_command.dart
Expand Up @@ -31,8 +31,8 @@ enum NextVersionType {
/// A bugfix change.
PATCH,

/// The release of an existing prerelease version.
RELEASE,
/// The release of an existing pre-1.0 version.
V1_RELEASE,
}

/// The state of a package's version relative to the comparison base.
Expand All @@ -53,8 +53,8 @@ enum _CurrentVersionState {
unknown,
}

/// Returns the set of allowed next versions, with their change type, for
/// [version].
/// Returns the set of allowed next non-prerelease versions, with their change
/// type, for [version].
///
/// [newVersion] is used to check whether this is a pre-1.0 version bump, as
/// those have different semver rules.
Expand All @@ -78,17 +78,17 @@ Map<Version, NextVersionType> getAllowedNextVersions(
final int currentBuildNumber = version.build.first as int;
nextBuildNumber = currentBuildNumber + 1;
}
final Version preReleaseVersion = Version(
final Version nextBuildVersion = Version(
version.major,
version.minor,
version.patch,
build: nextBuildNumber.toString(),
);
allowedNextVersions.clear();
allowedNextVersions[version.nextMajor] = NextVersionType.RELEASE;
allowedNextVersions[version.nextMajor] = NextVersionType.V1_RELEASE;
allowedNextVersions[version.nextMinor] = NextVersionType.BREAKING_MAJOR;
allowedNextVersions[version.nextPatch] = NextVersionType.MINOR;
allowedNextVersions[preReleaseVersion] = NextVersionType.PATCH;
allowedNextVersions[nextBuildVersion] = NextVersionType.PATCH;
}
return allowedNextVersions;
}
Expand Down Expand Up @@ -337,12 +337,11 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}

// Check for reverts when doing local validation.
if (!getBoolArg(_againstPubFlag) && currentVersion < previousVersion) {
final Map<Version, NextVersionType> possibleVersionsFromNewVersion =
getAllowedNextVersions(currentVersion, newVersion: previousVersion);
// Since this skips validation, try to ensure that it really is likely
// to be a revert rather than a typo by checking that the transition
// from the lower version to the new version would have been valid.
if (possibleVersionsFromNewVersion.containsKey(previousVersion)) {
if (_shouldAllowVersionChange(
oldVersion: currentVersion, newVersion: previousVersion)) {
logWarning('${indentation}New version is lower than previous version. '
'This is assumed to be a revert.');
return _CurrentVersionState.validRevert;
Expand All @@ -352,7 +351,8 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
final Map<Version, NextVersionType> allowedNextVersions =
getAllowedNextVersions(previousVersion, newVersion: currentVersion);

if (allowedNextVersions.containsKey(currentVersion)) {
if (_shouldAllowVersionChange(
oldVersion: previousVersion, newVersion: currentVersion)) {
print('$indentation$previousVersion -> $currentVersion');
} else {
printError('${indentation}Incorrectly updated version.\n'
Expand All @@ -361,7 +361,13 @@ ${indentation}HTTP response: ${pubVersionFinderResponse.httpResponse.body}
return _CurrentVersionState.invalidChange;
}

if (allowedNextVersions[currentVersion] == NextVersionType.BREAKING_MAJOR &&
// Check whether the version (or for a pre-release, the version that
// pre-release would eventually be released as) is a breaking change, and
// if so, validate it.
final Version targetReleaseVersion =
currentVersion.isPreRelease ? currentVersion.nextPatch : currentVersion;
if (allowedNextVersions[targetReleaseVersion] ==
NextVersionType.BREAKING_MAJOR &&
!_validateBreakingChange(package)) {
printError('${indentation}Breaking change detected.\n'
'${indentation}Breaking changes to platform interfaces are not '
Expand Down Expand Up @@ -520,6 +526,27 @@ ${indentation}The first version listed in CHANGELOG.md is $fromChangeLog.
return file.readAsStringSync();
}

/// Returns true if the given version transition should be allowed.
bool _shouldAllowVersionChange(
{required Version oldVersion, required Version newVersion}) {
// Get the non-pre-release next version mapping.
final Map<Version, NextVersionType> allowedNextVersions =
getAllowedNextVersions(oldVersion, newVersion: newVersion);

if (allowedNextVersions.containsKey(newVersion)) {
return true;
}
// Allow a pre-release version of a version that would be a valid
// transition.
if (newVersion.isPreRelease) {
final Version targetReleaseVersion = newVersion.nextPatch;
if (allowedNextVersions.containsKey(targetReleaseVersion)) {
return true;
}
}
return false;
}

/// Returns an error string if the changes to this package should have
/// resulted in a version change, or shoud have resulted in a CHANGELOG change
/// but didn't.
Expand Down
2 changes: 1 addition & 1 deletion script/tool/pubspec.yaml
@@ -1,7 +1,7 @@
name: flutter_plugin_tools
description: Productivity utils for flutter/plugins and flutter/packages
repository: https://github.com/flutter/plugins/tree/main/script/tool
version: 0.8.7
version: 0.8.8

dependencies:
args: ^2.1.0
Expand Down
180 changes: 180 additions & 0 deletions script/tool/test/version_check_command_test.dart
Expand Up @@ -1178,6 +1178,186 @@ ${indentation}HTTP response: null
]),
);
});

group('prelease versions', () {
test(
'allow an otherwise-valid transition that also adds a pre-release component',
() async {
createFakePlugin('plugin', packagesDir, version: '2.0.0-dev');
processRunner.mockProcessesForExecutable['git-show'] = <io.Process>[
MockProcess(stdout: 'version: 1.0.0'),
];
final List<String> output = await runCapturingPrint(
runner, <String>['version-check', '--base-sha=main']);

expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('1.0.0 -> 2.0.0-dev'),
]),
);
expect(
processRunner.recordedCalls,
containsAllInOrder(const <ProcessCall>[
ProcessCall('git-show',
<String>['main:packages/plugin/pubspec.yaml'], null)
]));
});

test('allow releasing a pre-release', () async {
createFakePlugin('plugin', packagesDir, version: '1.2.0');
processRunner.mockProcessesForExecutable['git-show'] = <io.Process>[
MockProcess(stdout: 'version: 1.2.0-dev'),
];
final List<String> output = await runCapturingPrint(
runner, <String>['version-check', '--base-sha=main']);

expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('1.2.0-dev -> 1.2.0'),
]),
);
expect(
processRunner.recordedCalls,
containsAllInOrder(const <ProcessCall>[
ProcessCall('git-show',
<String>['main:packages/plugin/pubspec.yaml'], null)
]));
});

// Allow abandoning a pre-release version in favor of a different version
// change type.
test(
'allow an otherwise-valid transition that also removes a pre-release component',
() async {
createFakePlugin('plugin', packagesDir, version: '2.0.0');
processRunner.mockProcessesForExecutable['git-show'] = <io.Process>[
MockProcess(stdout: 'version: 1.2.0-dev'),
];
final List<String> output = await runCapturingPrint(
runner, <String>['version-check', '--base-sha=main']);

expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('1.2.0-dev -> 2.0.0'),
]),
);
expect(
processRunner.recordedCalls,
containsAllInOrder(const <ProcessCall>[
ProcessCall('git-show',
<String>['main:packages/plugin/pubspec.yaml'], null)
]));
});

test('allow changing only the pre-release version', () async {
createFakePlugin('plugin', packagesDir, version: '1.2.0-dev.2');
processRunner.mockProcessesForExecutable['git-show'] = <io.Process>[
MockProcess(stdout: 'version: 1.2.0-dev.1'),
];
final List<String> output = await runCapturingPrint(
runner, <String>['version-check', '--base-sha=main']);

expect(
output,
containsAllInOrder(<Matcher>[
contains('Running for plugin'),
contains('1.2.0-dev.1 -> 1.2.0-dev.2'),
]),
);
expect(
processRunner.recordedCalls,
containsAllInOrder(const <ProcessCall>[
ProcessCall('git-show',
<String>['main:packages/plugin/pubspec.yaml'], null)
]));
});

test('denies invalid version change that also adds a pre-release',
() async {
createFakePlugin('plugin', packagesDir, version: '0.2.0-dev');
processRunner.mockProcessesForExecutable['git-show'] = <io.Process>[
MockProcess(stdout: 'version: 0.0.1'),
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['version-check', '--base-sha=main'],
errorHandler: (Error e) {
commandError = e;
});

expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Incorrectly updated version.'),
]));
expect(
processRunner.recordedCalls,
containsAllInOrder(const <ProcessCall>[
ProcessCall('git-show',
<String>['main:packages/plugin/pubspec.yaml'], null)
]));
});

test('denies invalid version change that also removes a pre-release',
() async {
createFakePlugin('plugin', packagesDir, version: '0.2.0');
processRunner.mockProcessesForExecutable['git-show'] = <io.Process>[
MockProcess(stdout: 'version: 0.0.1-dev'),
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['version-check', '--base-sha=main'],
errorHandler: (Error e) {
commandError = e;
});

expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Incorrectly updated version.'),
]));
expect(
processRunner.recordedCalls,
containsAllInOrder(const <ProcessCall>[
ProcessCall('git-show',
<String>['main:packages/plugin/pubspec.yaml'], null)
]));
});

test('denies invalid version change between pre-releases', () async {
createFakePlugin('plugin', packagesDir, version: '0.2.0-dev');
processRunner.mockProcessesForExecutable['git-show'] = <io.Process>[
MockProcess(stdout: 'version: 0.0.1-dev'),
];
Error? commandError;
final List<String> output = await runCapturingPrint(
runner, <String>['version-check', '--base-sha=main'],
errorHandler: (Error e) {
commandError = e;
});

expect(commandError, isA<ToolExit>());
expect(
output,
containsAllInOrder(<Matcher>[
contains('Incorrectly updated version.'),
]));
expect(
processRunner.recordedCalls,
containsAllInOrder(const <ProcessCall>[
ProcessCall('git-show',
<String>['main:packages/plugin/pubspec.yaml'], null)
]));
});
});
});

group('Pre 1.0', () {
Expand Down