Unverified Commit d9634155 authored by Casey Hillers's avatar Casey Hillers Committed by GitHub

[conductor] Remove PublishChannel and use MPA command (#135884)

Move more of the playbook into conductor. The MPA command inputs are prone to human error.
parent e141d0a4
......@@ -18,7 +18,7 @@ const List<String> kReleaseChannels = <String>[...kBaseReleaseChannels, Framewor
const String kReleaseDocumentationUrl = 'https://github.com/flutter/flutter/wiki/Flutter-Cherrypick-Process';
const String kLuciPackagingConsoleLink = 'https://ci.chromium.org/p/flutter/g/packaging/console';
const String kLuciPackagingConsoleLink = 'https://ci.chromium.org/p/dart-internal/g/flutter_packaging/console';
const String kWebsiteReleasesUrl = 'https://docs.flutter.dev/development/tools/sdk/releases';
......
......@@ -141,15 +141,12 @@ class NextContext extends Context {
}
await pushWorkingBranch(engine, state.engine);
case pb.ReleasePhase.CODESIGN_ENGINE_BINARIES:
stdio.printStatus(<String>[
'You must validate pre-submit CI for your engine PR, merge it, and codesign',
'binaries before proceeding.\n',
].join('\n'));
case pb.ReleasePhase.VERIFY_ENGINE_CI:
stdio.printStatus('You must validate post-submit CI for your engine PR and merge it');
if (!autoAccept) {
// TODO(fujino): actually test if binaries have been codesigned on macOS
final bool response = await prompt(
'Has CI passed for the engine PR and binaries been codesigned?',
'Has CI passed for the engine PR?\n\n'
'${state_import.luciConsoleLink(state.releaseChannel, 'engine')}'
);
if (!response) {
stdio.printError('Aborting command.');
......@@ -190,10 +187,10 @@ class NextContext extends Context {
addFirst: true,
);
// append to list of cherrypicks so we know a PR is required
state.framework.cherrypicks.add(pb.Cherrypick(
appliedRevision: revision,
state: pb.CherrypickState.COMPLETED,
));
state.framework.cherrypicks.add(pb.Cherrypick.create()
..appliedRevision = revision
..state = pb.CherrypickState.COMPLETED
);
}
stdio.printStatus('Rolling new engine hash $engineRevision to framework checkout...');
needsCommit = await framework.updateEngineRevision(engineRevision);
......@@ -203,10 +200,10 @@ class NextContext extends Context {
addFirst: true,
);
// append to list of cherrypicks so we know a PR is required
state.framework.cherrypicks.add(pb.Cherrypick(
appliedRevision: revision,
state: pb.CherrypickState.COMPLETED,
));
state.framework.cherrypicks.add(pb.Cherrypick.create()
..appliedRevision = revision
..state = pb.CherrypickState.COMPLETED
);
}
final List<pb.Cherrypick> unappliedCherrypicks = <pb.Cherrypick>[];
......@@ -250,83 +247,18 @@ class NextContext extends Context {
await pushWorkingBranch(framework, state.framework);
case pb.ReleasePhase.PUBLISH_VERSION:
stdio.printStatus('Please ensure that you have merged your framework PR and that');
stdio.printStatus('post-submit CI has finished successfully.\n');
final Remote frameworkUpstream = Remote(
name: RemoteName.upstream,
url: state.framework.upstream.url,
);
final FrameworkRepository framework = FrameworkRepository(
checkouts,
// We explicitly want to check out the merged version from upstream
initialRef: '${frameworkUpstream.name}/${state.framework.candidateBranch}',
upstreamRemote: frameworkUpstream,
previousCheckoutLocation: state.framework.checkoutPath,
);
final String frameworkHead = await framework.reverseParse('HEAD');
final Remote engineUpstream = Remote(
name: RemoteName.upstream,
url: state.engine.upstream.url,
);
final EngineRepository engine = EngineRepository(
checkouts,
// We explicitly want to check out the merged version from upstream
initialRef: '${engineUpstream.name}/${state.engine.candidateBranch}',
upstreamRemote: engineUpstream,
previousCheckoutLocation: state.engine.checkoutPath,
);
final String engineHead = await engine.reverseParse('HEAD');
if (!autoAccept) {
final bool response = await prompt(
'Are you ready to tag commit $frameworkHead as ${state.releaseVersion}\n'
'and push to remote ${state.framework.upstream.url}?',
);
if (!response) {
stdio.printError('Aborting command.');
updateState(state, stdio.logs);
return;
}
}
await framework.tag(frameworkHead, state.releaseVersion, frameworkUpstream.name);
await engine.tag(engineHead, state.releaseVersion, engineUpstream.name);
case pb.ReleasePhase.PUBLISH_CHANNEL:
final Remote upstream = Remote(
name: RemoteName.upstream,
url: state.framework.upstream.url,
);
final FrameworkRepository framework = FrameworkRepository(
checkouts,
// We explicitly want to check out the merged version from upstream
initialRef: '${upstream.name}/${state.framework.candidateBranch}',
upstreamRemote: upstream,
previousCheckoutLocation: state.framework.checkoutPath,
);
final String headRevision = await framework.reverseParse('HEAD');
if (!autoAccept) {
// dryRun: true means print out git command
await framework.pushRef(
fromRef: headRevision,
toRef: state.releaseChannel,
remote: state.framework.upstream.url,
force: force,
dryRun: true,
);
final bool response = await prompt(
'Are you ready to publish version ${state.releaseVersion} to ${state.releaseChannel}?',
);
if (!response) {
stdio.printError('Aborting command.');
updateState(state, stdio.logs);
return;
}
}
await framework.pushRef(
fromRef: headRevision,
toRef: state.releaseChannel,
remote: state.framework.upstream.url,
force: force,
);
final String command = '''
tool-proxy-cli --tool_proxy=/abns/dart-eng-tool-proxy/prod-dart-eng-tool-proxy-tool-proxy.annealed-tool-proxy \\
--block_on_mpa -I flutter_release \\
:git_branch ${state.framework.candidateBranch} \\
:release_channel ${state.releaseChannel} \\
:tag ${state.releaseVersion} \\
:force false
''';
stdio.printStatus('Please ensure that you have merged your framework PR');
stdio.printStatus('and post-submit CI has finished successfully.\n');
stdio.printStatus('Run the following command, and ask a Googler');
stdio.printStatus('to review the request\n\n$command');
case pb.ReleasePhase.VERIFY_RELEASE:
stdio.printStatus(
'The current status of packaging builds can be seen at:\n'
......
......@@ -2,39 +2,36 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
///
//
// Generated code. Do not modify.
// source: conductor_state.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
// ignore_for_file: UNDEFINED_SHOWN_NAME
// ignore_for_file: annotate_overrides, camel_case_types
// ignore_for_file: constant_identifier_names, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
import 'dart:core' as $core;
import 'package:protobuf/protobuf.dart' as $pb;
class ReleasePhase extends $pb.ProtobufEnum {
static const ReleasePhase APPLY_ENGINE_CHERRYPICKS =
ReleasePhase._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'APPLY_ENGINE_CHERRYPICKS');
static const ReleasePhase CODESIGN_ENGINE_BINARIES =
ReleasePhase._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CODESIGN_ENGINE_BINARIES');
static const ReleasePhase APPLY_FRAMEWORK_CHERRYPICKS = ReleasePhase._(
2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'APPLY_FRAMEWORK_CHERRYPICKS');
static const ReleasePhase PUBLISH_VERSION =
ReleasePhase._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PUBLISH_VERSION');
static const ReleasePhase PUBLISH_CHANNEL =
ReleasePhase._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PUBLISH_CHANNEL');
static const ReleasePhase VERIFY_RELEASE =
ReleasePhase._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'VERIFY_RELEASE');
static const ReleasePhase RELEASE_COMPLETED =
ReleasePhase._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'RELEASE_COMPLETED');
ReleasePhase._(0, _omitEnumNames ? '' : 'APPLY_ENGINE_CHERRYPICKS');
static const ReleasePhase VERIFY_ENGINE_CI = ReleasePhase._(1, _omitEnumNames ? '' : 'VERIFY_ENGINE_CI');
static const ReleasePhase APPLY_FRAMEWORK_CHERRYPICKS =
ReleasePhase._(2, _omitEnumNames ? '' : 'APPLY_FRAMEWORK_CHERRYPICKS');
static const ReleasePhase PUBLISH_VERSION = ReleasePhase._(3, _omitEnumNames ? '' : 'PUBLISH_VERSION');
static const ReleasePhase VERIFY_RELEASE = ReleasePhase._(5, _omitEnumNames ? '' : 'VERIFY_RELEASE');
static const ReleasePhase RELEASE_COMPLETED = ReleasePhase._(6, _omitEnumNames ? '' : 'RELEASE_COMPLETED');
static const $core.List<ReleasePhase> values = <ReleasePhase>[
APPLY_ENGINE_CHERRYPICKS,
CODESIGN_ENGINE_BINARIES,
VERIFY_ENGINE_CI,
APPLY_FRAMEWORK_CHERRYPICKS,
PUBLISH_VERSION,
PUBLISH_CHANNEL,
VERIFY_RELEASE,
RELEASE_COMPLETED,
];
......@@ -46,14 +43,11 @@ class ReleasePhase extends $pb.ProtobufEnum {
}
class CherrypickState extends $pb.ProtobufEnum {
static const CherrypickState PENDING =
CherrypickState._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PENDING');
static const CherrypickState PENDING = CherrypickState._(0, _omitEnumNames ? '' : 'PENDING');
static const CherrypickState PENDING_WITH_CONFLICT =
CherrypickState._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PENDING_WITH_CONFLICT');
static const CherrypickState COMPLETED =
CherrypickState._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'COMPLETED');
static const CherrypickState ABANDONED =
CherrypickState._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ABANDONED');
CherrypickState._(1, _omitEnumNames ? '' : 'PENDING_WITH_CONFLICT');
static const CherrypickState COMPLETED = CherrypickState._(2, _omitEnumNames ? '' : 'COMPLETED');
static const CherrypickState ABANDONED = CherrypickState._(3, _omitEnumNames ? '' : 'ABANDONED');
static const $core.List<CherrypickState> values = <CherrypickState>[
PENDING,
......@@ -69,14 +63,10 @@ class CherrypickState extends $pb.ProtobufEnum {
}
class ReleaseType extends $pb.ProtobufEnum {
static const ReleaseType STABLE_INITIAL =
ReleaseType._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'STABLE_INITIAL');
static const ReleaseType STABLE_HOTFIX =
ReleaseType._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'STABLE_HOTFIX');
static const ReleaseType BETA_INITIAL =
ReleaseType._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'BETA_INITIAL');
static const ReleaseType BETA_HOTFIX =
ReleaseType._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'BETA_HOTFIX');
static const ReleaseType STABLE_INITIAL = ReleaseType._(0, _omitEnumNames ? '' : 'STABLE_INITIAL');
static const ReleaseType STABLE_HOTFIX = ReleaseType._(1, _omitEnumNames ? '' : 'STABLE_HOTFIX');
static const ReleaseType BETA_INITIAL = ReleaseType._(2, _omitEnumNames ? '' : 'BETA_INITIAL');
static const ReleaseType BETA_HOTFIX = ReleaseType._(3, _omitEnumNames ? '' : 'BETA_HOTFIX');
static const $core.List<ReleaseType> values = <ReleaseType>[
STABLE_INITIAL,
......@@ -90,3 +80,5 @@ class ReleaseType extends $pb.ProtobufEnum {
const ReleaseType._($core.int v, $core.String n) : super(v, n);
}
const _omitEnumNames = $core.bool.fromEnvironment('protobuf.omit_enum_names');
......@@ -2,11 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
///
//
// Generated code. Do not modify.
// source: conductor_state.proto
//
// @dart = 2.12
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
// ignore_for_file: annotate_overrides, camel_case_types
// ignore_for_file: constant_identifier_names
// ignore_for_file: deprecated_member_use_from_same_package, library_prefixes
// ignore_for_file: non_constant_identifier_names, prefer_final_fields
// ignore_for_file: unnecessary_import, unnecessary_this, unused_import
export 'conductor_state.pb.dart';
......@@ -11,16 +11,16 @@ message Remote {
enum ReleasePhase {
// Release was started with `conductor start` and repositories cloned.
APPLY_ENGINE_CHERRYPICKS = 0;
CODESIGN_ENGINE_BINARIES = 1;
// Verify engine CI is green before opening framework PR.
VERIFY_ENGINE_CI = 1;
APPLY_FRAMEWORK_CHERRYPICKS = 2;
// Git tag applied to framework RC branch HEAD and pushed upstream.
PUBLISH_VERSION = 3;
// RC branch HEAD pushed to upstream release branch.
//
// For example, flutter-1.2-candidate.3 -> upstream/beta
PUBLISH_CHANNEL = 4;
reserved 4; // Formerly PUBLISH_CHANNEL, merged into PUBLISH_VERSION.
// Package artifacts verified to exist on cloud storage.
VERIFY_RELEASE = 5;
......
......@@ -310,16 +310,24 @@ class StartContext extends Context {
}
final String engineHead = await engine.reverseParse('HEAD');
state.engine = pb.Repository(
candidateBranch: candidateBranch,
workingBranch: workingBranchName,
startingGitHead: engineHead,
currentGitHead: engineHead,
checkoutPath: (await engine.checkoutDirectory).path,
dartRevision: dartRevision,
upstream: pb.Remote(name: 'upstream', url: engine.upstreamRemote.url),
mirror: pb.Remote(name: 'mirror', url: engine.mirrorRemote!.url),
state.engine = (pb.Repository.create()
..candidateBranch = candidateBranch
..workingBranch = workingBranchName
..startingGitHead = engineHead
..currentGitHead = engineHead
..checkoutPath = (await engine.checkoutDirectory).path
..upstream = (pb.Remote.create()
..name = 'upstream'
..url = engine.upstreamRemote.url
)
..mirror = (pb.Remote.create()
..name = 'mirror'
..url = engine.mirrorRemote!.url
)
);
if (dartRevision != null && dartRevision!.isNotEmpty) {
state.engine.dartRevision = dartRevision!;
}
await framework.newBranch(workingBranchName);
......@@ -362,14 +370,20 @@ class StartContext extends Context {
state.releaseVersion = nextVersion.toString();
state.framework = pb.Repository(
candidateBranch: candidateBranch,
workingBranch: workingBranchName,
startingGitHead: frameworkHead,
currentGitHead: frameworkHead,
checkoutPath: (await framework.checkoutDirectory).path,
upstream: pb.Remote(name: 'upstream', url: framework.upstreamRemote.url),
mirror: pb.Remote(name: 'mirror', url: framework.mirrorRemote!.url),
state.framework = (pb.Repository.create()
..candidateBranch = candidateBranch
..workingBranch = workingBranchName
..startingGitHead = frameworkHead
..currentGitHead = frameworkHead
..checkoutPath = (await framework.checkoutDirectory).path
..upstream = (pb.Remote.create()
..name = 'upstream'
..url = framework.upstreamRemote.url
)
..mirror = (pb.Remote.create()
..name = 'mirror'
..url = framework.mirrorRemote!.url
)
);
state.currentPhase = ReleasePhase.APPLY_ENGINE_CHERRYPICKS;
......
......@@ -50,7 +50,7 @@ const String stablePostReleaseMsg = """
String luciConsoleLink(String channel, String groupName) {
assert(
globals.kReleaseChannels.contains(channel),
'channel $channel not recognized',
'channel "$channel" not recognized',
);
assert(
<String>['flutter', 'engine', 'packaging'].contains(groupName),
......@@ -177,10 +177,10 @@ String phaseInstructions(pb.ConductorState state) {
'\t${cherrypick.trunkRevision}',
'See ${globals.kReleaseDocumentationUrl} for more information.',
].join('\n');
case ReleasePhase.CODESIGN_ENGINE_BINARIES:
case ReleasePhase.VERIFY_ENGINE_CI:
if (!requiresEnginePR(state)) {
return 'You must now codesign the engine binaries for commit '
'${state.engine.startingGitHead}.';
return 'You must verify engine CI has passed: '
'${luciConsoleLink(state.releaseChannel, 'engine')}';
}
// User's working branch was pushed to their mirror, but a PR needs to be
// opened on GitHub.
......@@ -231,8 +231,6 @@ String phaseInstructions(pb.ConductorState state) {
'verify pre-submit CI builds on your pull request are successful, merge your ',
'pull request, validate post-submit CI.',
].join('\n');
case ReleasePhase.PUBLISH_CHANNEL:
return 'Issue `conductor next` to publish your release to the release branch.';
case ReleasePhase.VERIFY_RELEASE:
return 'Release archive packages must be verified on cloud storage: ${luciConsoleLink(state.releaseChannel, 'packaging')}';
case ReleasePhase.RELEASE_COMPLETED:
......@@ -286,11 +284,20 @@ 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) {
final ReleasePhase? nextPhase = ReleasePhase.valueOf(currentPhase.value + 1);
if (nextPhase == null) {
throw globals.ConductorException('There is no next ReleasePhase!');
switch (currentPhase) {
case ReleasePhase.PUBLISH_VERSION:
return ReleasePhase.VERIFY_RELEASE;
case ReleasePhase.APPLY_ENGINE_CHERRYPICKS:
case ReleasePhase.VERIFY_ENGINE_CI:
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;
}
}
return nextPhase;
throw globals.ConductorException('There is no next ReleasePhase!');
}
// Indent two spaces.
......
......@@ -31,25 +31,27 @@ void main() {
late pb.ConductorState state;
setUp(() {
state = pb.ConductorState(
engine: pb.Repository(
candidateBranch: candidateBranch,
cherrypicks: <pb.Cherrypick>[
pb.Cherrypick(trunkRevision: engineCherrypick1),
pb.Cherrypick(trunkRevision: engineCherrypick2),
],
dartRevision: dartRevision,
workingBranch: workingBranch,
),
framework: pb.Repository(
candidateBranch: candidateBranch,
cherrypicks: <pb.Cherrypick>[
pb.Cherrypick(trunkRevision: frameworkCherrypick),
],
workingBranch: workingBranch,
),
releaseChannel: releaseChannel,
releaseVersion: releaseVersion,
state = (pb.ConductorState.create()
..engine = (pb.Repository.create()
..candidateBranch = candidateBranch
..cherrypicks.addAll(<pb.Cherrypick>[
pb.Cherrypick.create()
..trunkRevision = engineCherrypick1,
pb.Cherrypick.create()
..trunkRevision = engineCherrypick2,
])
..dartRevision = dartRevision
..workingBranch = workingBranch
)
..framework = (pb.Repository.create()
..candidateBranch = candidateBranch
..cherrypicks.add(pb.Cherrypick.create()
..trunkRevision = frameworkCherrypick
)
..workingBranch = workingBranch
)
..releaseChannel = releaseChannel
..releaseVersion = releaseVersion
);
});
......
This diff is collapsed.
......@@ -15,24 +15,23 @@ void main() {
final File stateFile = fileSystem.file('/path/to/statefile.json')
..createSync(recursive: true);
const String candidateBranch = 'flutter-2.3-candidate.0';
final pb.ConductorState state = pb.ConductorState(
releaseChannel: 'stable',
releaseVersion: '2.3.4',
engine: pb.Repository(
candidateBranch: candidateBranch,
upstream: pb.Remote(
name: 'upstream',
url: 'git@github.com:flutter/engine.git',
),
),
framework: pb.Repository(
candidateBranch: candidateBranch,
upstream: pb.Remote(
name: 'upstream',
url: 'git@github.com:flutter/flutter.git',
),
),
);
final pb.ConductorState state = pb.ConductorState.create()
..releaseChannel = 'stable'
..releaseVersion = '2.3.4'
..engine = (pb.Repository.create()
..candidateBranch = candidateBranch
..upstream = (pb.Remote.create()
..name = 'upstream'
..url = 'git@github.com:flutter/engine.git'
)
)
..framework = (pb.Repository.create()
..candidateBranch = candidateBranch
..upstream = (pb.Remote.create()
..name = 'upstream'
..url = 'git@github.com:flutter/flutter.git'
)
);
writeStateToFile(
stateFile,
state,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment