Update conductor to write engine.version file (#163350)
This adds a new phase to the conductor after applying cherrypicks, to update the engine.version file with the revision from the previous commit. Note, this will produce a different PR, because it has to be in a different commit after squash & merge. Automates https://github.com/flutter/flutter/issues/162265
This commit is contained in:
parent
89f1eba3c8
commit
eb66d03350
@ -150,6 +150,51 @@ class NextContext extends Context {
|
||||
}
|
||||
}
|
||||
|
||||
await pushWorkingBranch(framework, state.framework);
|
||||
case pb.ReleasePhase.UPDATE_ENGINE_VERSION:
|
||||
final Remote upstream = Remote.upstream(state.framework.upstream.url);
|
||||
final FrameworkRepository framework = FrameworkRepository(
|
||||
checkouts,
|
||||
initialRef: state.framework.workingBranch,
|
||||
upstreamRemote: upstream,
|
||||
previousCheckoutLocation: state.framework.checkoutPath,
|
||||
);
|
||||
final String rev = await framework.reverseParse('HEAD');
|
||||
final File engineVersionFile = (await framework.checkoutDirectory)
|
||||
.childDirectory('bin')
|
||||
.childDirectory('internal')
|
||||
.childFile('engine.version');
|
||||
|
||||
engineVersionFile.writeAsStringSync(rev);
|
||||
|
||||
// Must force add since it is gitignored
|
||||
await framework.git.run(
|
||||
const <String>['add', 'bin/internal/engine.version', '--force'],
|
||||
'adding engine.version file',
|
||||
workingDirectory: (await framework.checkoutDirectory).path,
|
||||
);
|
||||
final String revision = await framework.commit(
|
||||
'Create engine.version file pointing to $rev',
|
||||
);
|
||||
// append to list of cherrypicks so we know a PR is required
|
||||
state.framework.cherrypicks.add(
|
||||
pb.Cherrypick.create()
|
||||
..appliedRevision = revision
|
||||
..state = pb.CherrypickState.COMPLETED,
|
||||
);
|
||||
|
||||
if (!autoAccept) {
|
||||
final bool response = await prompt(
|
||||
'Are you ready to push your framework branch to the repository '
|
||||
'${state.framework.mirror.url}?',
|
||||
);
|
||||
if (!response) {
|
||||
stdio.printError('Aborting command.');
|
||||
updateState(state, stdio.logs);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
await pushWorkingBranch(framework, state.framework);
|
||||
case pb.ReleasePhase.PUBLISH_VERSION:
|
||||
final String command = '''
|
||||
|
@ -20,15 +20,18 @@ import 'package:protobuf/protobuf.dart' as $pb;
|
||||
class ReleasePhase extends $pb.ProtobufEnum {
|
||||
static const ReleasePhase APPLY_FRAMEWORK_CHERRYPICKS =
|
||||
ReleasePhase._(0, _omitEnumNames ? '' : 'APPLY_FRAMEWORK_CHERRYPICKS');
|
||||
static const ReleasePhase UPDATE_ENGINE_VERSION =
|
||||
ReleasePhase._(1, _omitEnumNames ? '' : 'UPDATE_ENGINE_VERSION');
|
||||
static const ReleasePhase PUBLISH_VERSION =
|
||||
ReleasePhase._(1, _omitEnumNames ? '' : 'PUBLISH_VERSION');
|
||||
ReleasePhase._(2, _omitEnumNames ? '' : 'PUBLISH_VERSION');
|
||||
static const ReleasePhase VERIFY_RELEASE =
|
||||
ReleasePhase._(2, _omitEnumNames ? '' : 'VERIFY_RELEASE');
|
||||
ReleasePhase._(3, _omitEnumNames ? '' : 'VERIFY_RELEASE');
|
||||
static const ReleasePhase RELEASE_COMPLETED =
|
||||
ReleasePhase._(3, _omitEnumNames ? '' : 'RELEASE_COMPLETED');
|
||||
ReleasePhase._(4, _omitEnumNames ? '' : 'RELEASE_COMPLETED');
|
||||
|
||||
static const $core.List<ReleasePhase> values = <ReleasePhase>[
|
||||
APPLY_FRAMEWORK_CHERRYPICKS,
|
||||
UPDATE_ENGINE_VERSION,
|
||||
PUBLISH_VERSION,
|
||||
VERIFY_RELEASE,
|
||||
RELEASE_COMPLETED,
|
||||
|
@ -22,16 +22,18 @@ const ReleasePhase$json = {
|
||||
'1': 'ReleasePhase',
|
||||
'2': [
|
||||
{'1': 'APPLY_FRAMEWORK_CHERRYPICKS', '2': 0},
|
||||
{'1': 'PUBLISH_VERSION', '2': 1},
|
||||
{'1': 'VERIFY_RELEASE', '2': 2},
|
||||
{'1': 'RELEASE_COMPLETED', '2': 3},
|
||||
{'1': 'UPDATE_ENGINE_VERSION', '2': 1},
|
||||
{'1': 'PUBLISH_VERSION', '2': 2},
|
||||
{'1': 'VERIFY_RELEASE', '2': 3},
|
||||
{'1': 'RELEASE_COMPLETED', '2': 4},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ReleasePhase`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List releasePhaseDescriptor = $convert
|
||||
.base64Decode('CgxSZWxlYXNlUGhhc2USHwobQVBQTFlfRlJBTUVXT1JLX0NIRVJSWVBJQ0tTEAASEwoPUFVCTE'
|
||||
'lTSF9WRVJTSU9OEAESEgoOVkVSSUZZX1JFTEVBU0UQAhIVChFSRUxFQVNFX0NPTVBMRVRFRBAD');
|
||||
.base64Decode('CgxSZWxlYXNlUGhhc2USHwobQVBQTFlfRlJBTUVXT1JLX0NIRVJSWVBJQ0tTEAASGQoVVVBEQV'
|
||||
'RFX0VOR0lORV9WRVJTSU9OEAESEwoPUFVCTElTSF9WRVJTSU9OEAISEgoOVkVSSUZZX1JFTEVB'
|
||||
'U0UQAxIVChFSRUxFQVNFX0NPTVBMRVRFRBAE');
|
||||
|
||||
@$core.Deprecated('Use cherrypickStateDescriptor instead')
|
||||
const CherrypickState$json = {
|
||||
|
@ -11,14 +11,16 @@ message Remote {
|
||||
enum ReleasePhase {
|
||||
APPLY_FRAMEWORK_CHERRYPICKS = 0;
|
||||
|
||||
UPDATE_ENGINE_VERSION = 1;
|
||||
|
||||
// Git tag applied to framework RC branch HEAD and pushed upstream.
|
||||
PUBLISH_VERSION = 1;
|
||||
PUBLISH_VERSION = 2;
|
||||
|
||||
// Package artifacts verified to exist on cloud storage.
|
||||
VERIFY_RELEASE = 2;
|
||||
VERIFY_RELEASE = 3;
|
||||
|
||||
// There is no further work to be done.
|
||||
RELEASE_COMPLETED = 3;
|
||||
RELEASE_COMPLETED = 4;
|
||||
}
|
||||
|
||||
enum CherrypickState {
|
||||
|
@ -411,16 +411,18 @@ abstract class Repository {
|
||||
}
|
||||
|
||||
Future<String> commit(String message, {bool addFirst = false, String? author}) async {
|
||||
final bool hasChanges =
|
||||
(await git.getOutput(
|
||||
<String>['status', '--porcelain'],
|
||||
'check for uncommitted changes',
|
||||
workingDirectory: (await checkoutDirectory).path,
|
||||
)).trim().isNotEmpty;
|
||||
if (!hasChanges) {
|
||||
throw ConductorException('Tried to commit with message $message but no changes were present');
|
||||
}
|
||||
if (addFirst) {
|
||||
final bool hasChanges =
|
||||
(await git.getOutput(
|
||||
<String>['status', '--porcelain'],
|
||||
'check for uncommitted changes',
|
||||
workingDirectory: (await checkoutDirectory).path,
|
||||
)).trim().isNotEmpty;
|
||||
if (!hasChanges) {
|
||||
throw ConductorException(
|
||||
'Tried to commit with message $message but no changes were present',
|
||||
);
|
||||
}
|
||||
await git.run(
|
||||
<String>['add', '--all'],
|
||||
'add all changes to the index',
|
||||
|
@ -156,6 +156,8 @@ String phaseInstructions(pb.ConductorState state) {
|
||||
return <String>[
|
||||
'Either all cherrypicks have been auto-applied or there were none.',
|
||||
].join('\n');
|
||||
case ReleasePhase.UPDATE_ENGINE_VERSION:
|
||||
return 'The conductor will now update the engine.version file to point at the previous commit.';
|
||||
case ReleasePhase.PUBLISH_VERSION:
|
||||
if (!requiresFrameworkPR(state)) {
|
||||
return 'Since there are no code changes in this release, no Framework '
|
||||
@ -223,18 +225,11 @@ String githubAccount(String remoteUrl) {
|
||||
/// Will throw a [ConductorException] if [ReleasePhase.RELEASE_COMPLETED] is
|
||||
/// passed as an argument, as there is no next phase.
|
||||
ReleasePhase getNextPhase(ReleasePhase currentPhase) {
|
||||
switch (currentPhase) {
|
||||
case ReleasePhase.PUBLISH_VERSION:
|
||||
return ReleasePhase.VERIFY_RELEASE;
|
||||
case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS:
|
||||
case ReleasePhase.VERIFY_RELEASE:
|
||||
case ReleasePhase.RELEASE_COMPLETED:
|
||||
final ReleasePhase? nextPhase = ReleasePhase.valueOf(currentPhase.value + 1);
|
||||
if (nextPhase != null) {
|
||||
return nextPhase;
|
||||
}
|
||||
final ReleasePhase? nextPhase = ReleasePhase.valueOf(currentPhase.value + 1);
|
||||
if (nextPhase != null) {
|
||||
return nextPhase;
|
||||
}
|
||||
throw globals.ConductorException('There is no next ReleasePhase!');
|
||||
throw globals.ConductorException('There is no next ReleasePhase after $currentPhase!');
|
||||
}
|
||||
|
||||
// Indent two spaces.
|
||||
|
@ -23,7 +23,7 @@ void main() {
|
||||
const String candidateBranch = 'flutter-1.2-candidate.3';
|
||||
const String workingBranch = 'cherrypicks-$candidateBranch';
|
||||
const String revision1 = 'd3af60d18e01fcb36e0c0fa06c8502e4935ed095';
|
||||
const String revision3 = 'ffffffffffffffffffffffffffffffffffffffff';
|
||||
const String revision2 = 'ffffffffffffffffffffffffffffffffffffffff';
|
||||
const String releaseVersion = '1.2.0-3.0.pre';
|
||||
const String releaseChannel = 'beta';
|
||||
const String stateFile = '/state-file.json';
|
||||
@ -70,7 +70,7 @@ void main() {
|
||||
);
|
||||
});
|
||||
|
||||
group('APPLY_FRAMEWORK_CHERRYPICKS to PUBLISH_VERSION', () {
|
||||
group('APPLY_FRAMEWORK_CHERRYPICKS to UPDATE_ENGINE_VERSION', () {
|
||||
const String mirrorRemoteUrl = 'https://github.com/org/repo.git';
|
||||
const String upstreamRemoteUrl = 'https://github.com/mirror/repo.git';
|
||||
const String engineUpstreamRemoteUrl = 'https://github.com/mirror/engine.git';
|
||||
@ -154,7 +154,7 @@ void main() {
|
||||
'Create candidate branch version $candidateBranch for $releaseChannel',
|
||||
],
|
||||
),
|
||||
const FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: revision3),
|
||||
const FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: revision2),
|
||||
]);
|
||||
writeStateToFile(fileSystem.file(stateFile), state, <String>[]);
|
||||
final Checkouts checkouts = Checkouts(
|
||||
@ -207,7 +207,7 @@ void main() {
|
||||
'Create candidate branch version $candidateBranch for $releaseChannel',
|
||||
],
|
||||
),
|
||||
const FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: revision3),
|
||||
const FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: revision2),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'push', 'mirror', 'HEAD:refs/heads/$workingBranch'],
|
||||
),
|
||||
@ -226,7 +226,7 @@ void main() {
|
||||
|
||||
final pb.ConductorState finalState = readStateFromFile(fileSystem.file(stateFile));
|
||||
|
||||
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
||||
expect(finalState.currentPhase, ReleasePhase.UPDATE_ENGINE_VERSION);
|
||||
expect(stdio.stdout, contains('There was 1 cherrypick that was not auto-applied'));
|
||||
expect(
|
||||
stdio.stdout,
|
||||
@ -241,6 +241,117 @@ void main() {
|
||||
expect(stdio.error, isEmpty);
|
||||
});
|
||||
});
|
||||
group('UPDATE_ENGINE_VERSION to PUBLISH_VERSION', () {
|
||||
const String mirrorRemoteUrl = 'https://github.com/org/repo.git';
|
||||
const String upstreamRemoteUrl = 'https://github.com/mirror/repo.git';
|
||||
const String engineUpstreamRemoteUrl = 'https://github.com/mirror/engine.git';
|
||||
const String frameworkCheckoutPath = '$checkoutsParentDirectory/framework';
|
||||
const String engineCheckoutPath = '$checkoutsParentDirectory/engine';
|
||||
const String oldEngineVersion = '000000001';
|
||||
|
||||
late FakeProcessManager processManager;
|
||||
late FakePlatform platform;
|
||||
late pb.ConductorState state;
|
||||
|
||||
setUp(() {
|
||||
processManager = FakeProcessManager.empty();
|
||||
platform = FakePlatform(
|
||||
environment: <String, String>{
|
||||
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||
},
|
||||
operatingSystem: localOperatingSystem,
|
||||
pathSeparator: localPathSeparator,
|
||||
);
|
||||
state =
|
||||
(pb.ConductorState.create()
|
||||
..releaseChannel = releaseChannel
|
||||
..releaseVersion = releaseVersion
|
||||
..framework =
|
||||
(pb.Repository.create()
|
||||
..candidateBranch = candidateBranch
|
||||
..checkoutPath = frameworkCheckoutPath
|
||||
..mirror =
|
||||
(pb.Remote.create()
|
||||
..name = 'mirror'
|
||||
..url = mirrorRemoteUrl)
|
||||
..upstream =
|
||||
(pb.Remote.create()
|
||||
..name = 'upstream'
|
||||
..url = upstreamRemoteUrl)
|
||||
..workingBranch = workingBranch)
|
||||
..engine =
|
||||
(pb.Repository.create()
|
||||
..candidateBranch = candidateBranch
|
||||
..checkoutPath = engineCheckoutPath
|
||||
..dartRevision = 'cdef0123'
|
||||
..workingBranch = workingBranch
|
||||
..upstream =
|
||||
(pb.Remote.create()
|
||||
..name = 'upstream'
|
||||
..url = engineUpstreamRemoteUrl))
|
||||
..currentPhase = ReleasePhase.UPDATE_ENGINE_VERSION);
|
||||
// create engine repo
|
||||
fileSystem.directory(engineCheckoutPath).createSync(recursive: true);
|
||||
// create framework repo
|
||||
final Directory frameworkDir = fileSystem.directory(frameworkCheckoutPath);
|
||||
final File engineRevisionFile = frameworkDir
|
||||
.childDirectory('bin')
|
||||
.childDirectory('internal')
|
||||
.childFile('engine.version');
|
||||
engineRevisionFile.createSync(recursive: true);
|
||||
engineRevisionFile.writeAsStringSync(oldEngineVersion, flush: true);
|
||||
});
|
||||
|
||||
test('creates a PR with an updated engine.version file', () async {
|
||||
// Respond "yes" to the prompt to push branch
|
||||
stdio.stdin.add('y');
|
||||
processManager.addCommands(const <FakeCommand>[
|
||||
FakeCommand(command: <String>['git', 'fetch', 'upstream']),
|
||||
FakeCommand(command: <String>['git', 'checkout', 'cherrypicks-$candidateBranch']),
|
||||
FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: revision1),
|
||||
FakeCommand(command: <String>['git', 'add', 'bin/internal/engine.version', '--force']),
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
'git',
|
||||
'commit',
|
||||
'--message',
|
||||
'Create engine.version file pointing to $revision1',
|
||||
],
|
||||
),
|
||||
FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: revision2),
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
'git',
|
||||
'push',
|
||||
'mirror',
|
||||
'HEAD:refs/heads/cherrypicks-$candidateBranch',
|
||||
],
|
||||
),
|
||||
]);
|
||||
writeStateToFile(fileSystem.file(stateFile), state, <String>[]);
|
||||
final Checkouts checkouts = Checkouts(
|
||||
fileSystem: fileSystem,
|
||||
parentDirectory: fileSystem.directory(checkoutsParentDirectory)
|
||||
..createSync(recursive: true),
|
||||
platform: platform,
|
||||
processManager: processManager,
|
||||
stdio: stdio,
|
||||
);
|
||||
|
||||
final CommandRunner<void> runner = createRunner(checkouts: checkouts);
|
||||
await runner.run(<String>['next', '--$kStateOption', stateFile]);
|
||||
|
||||
final pb.ConductorState finalState = readStateFromFile(fileSystem.file(stateFile));
|
||||
|
||||
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
||||
expect(
|
||||
fileSystem
|
||||
.file('$frameworkCheckoutPath/bin/internal/engine.version')
|
||||
.readAsStringSync(),
|
||||
revision1,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
group('PUBLISH_VERSION to VERIFY_RELEASE', () {
|
||||
const String releaseVersion = '1.2.0-3.0.pre';
|
||||
|
@ -73,7 +73,7 @@ vars = {
|
||||
);
|
||||
});
|
||||
|
||||
test('commit() throws if there are no local changes to commit', () {
|
||||
test('commit() throws if there are no local changes to commit and addFirst = true', () {
|
||||
const String commit1 = 'abc123';
|
||||
const String commit2 = 'def456';
|
||||
const String message = 'This is a commit message.';
|
||||
@ -106,7 +106,7 @@ vars = {
|
||||
|
||||
final FrameworkRepository repo = FrameworkRepository(checkouts);
|
||||
expect(
|
||||
() async => repo.commit(message),
|
||||
() async => repo.commit(message, addFirst: true),
|
||||
throwsExceptionWith('Tried to commit with message $message but no changes were present'),
|
||||
);
|
||||
});
|
||||
@ -129,10 +129,6 @@ vars = {
|
||||
),
|
||||
const FakeCommand(command: <String>['git', 'checkout', FrameworkRepository.defaultBranch]),
|
||||
const FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: commit1),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'status', '--porcelain'],
|
||||
stdout: 'MM path/to/file.txt',
|
||||
),
|
||||
const FakeCommand(command: <String>['git', 'commit', '--message', message]),
|
||||
const FakeCommand(command: <String>['git', 'rev-parse', 'HEAD'], stdout: commit2),
|
||||
]);
|
||||
|
Loading…
x
Reference in New Issue
Block a user