mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Conductor add next (#84354)
This commit is contained in:
parent
567586b407
commit
c3822edbe8
@ -29,8 +29,8 @@ Releases are initialized with the `start` sub-command, like:
|
|||||||
conductor start \
|
conductor start \
|
||||||
--candidate-branch=flutter-2.2-candidate.10 \
|
--candidate-branch=flutter-2.2-candidate.10 \
|
||||||
--release-channel=beta \
|
--release-channel=beta \
|
||||||
--framework-mirror=git@github.com:flutter-contributor/flutter.git \
|
--framework-mirror=git@github.com:username/flutter.git \
|
||||||
--engine-mirror=git@github.com:flutter-contributor/engine.git \
|
--engine-mirror=git@github.com:username/engine.git \
|
||||||
--engine-cherrypicks=72114dafe28c8700f1d5d629c6ae9d34172ba395 \
|
--engine-cherrypicks=72114dafe28c8700f1d5d629c6ae9d34172ba395 \
|
||||||
--framework-cherrypicks=a3e66b396746f6581b2b7efd1b0d0f0074215128,d8d853436206e86f416236b930e97779b143a100 \
|
--framework-cherrypicks=a3e66b396746f6581b2b7efd1b0d0f0074215128,d8d853436206e86f416236b930e97779b143a100 \
|
||||||
--dart-revision=4511eb2a779a612d9d6b2012123575013e0aef12
|
--dart-revision=4511eb2a779a612d9d6b2012123575013e0aef12
|
||||||
@ -54,3 +54,51 @@ Upon successful completion of the release, the following command will remove the
|
|||||||
persistent state file:
|
persistent state file:
|
||||||
|
|
||||||
`conductor clean`
|
`conductor clean`
|
||||||
|
|
||||||
|
## Steps
|
||||||
|
|
||||||
|
Once the user has finished manual steps for each step, they proceed to the next
|
||||||
|
step with the command:
|
||||||
|
|
||||||
|
`conductor next`
|
||||||
|
|
||||||
|
### Apply Engine Cherrypicks
|
||||||
|
|
||||||
|
The tool will attempt to auto-apply all engine cherrypicks. However, any
|
||||||
|
cherrypicks that result in a merge conflict will be reverted and it is left to
|
||||||
|
the user to manually cherry-pick them (with the command `git cherry-pick
|
||||||
|
$REVISION`) and resolve the merge conflict in their checkout.
|
||||||
|
|
||||||
|
Once a PR is opened, the user must validate CI builds. If there are regressions
|
||||||
|
(or if the `licenses_check` fails, then
|
||||||
|
`//engine/ci/licenses_golden/licenses_third_party` must be updated to match the
|
||||||
|
output of the failing test), then the user must fix these tests in their local
|
||||||
|
checkout and push their changes again.
|
||||||
|
|
||||||
|
### Codesign Engine Binaries
|
||||||
|
|
||||||
|
The user must validate post-submit CI builds for their merged engine PR have
|
||||||
|
passed. A link to the web dashboard is available via `conductor status`. Once
|
||||||
|
the post-submit CI builds have all passed, the user must codesign engine
|
||||||
|
binaries for the **merged** engine commit.
|
||||||
|
|
||||||
|
### Apply Framework Cherrypicks
|
||||||
|
|
||||||
|
The tool will attempt to auto-apply all framework cherrypicks. However, any
|
||||||
|
cherrypicks that result in a merge conflict will be reverted and it is left to
|
||||||
|
the user to manually cherry-pick them (with the command `git cherry-pick
|
||||||
|
$REVISION`) and resolve the merge conflict in their checkout.
|
||||||
|
|
||||||
|
### Publish Version
|
||||||
|
|
||||||
|
This step will add a version git tag to the final Framework commit and push it
|
||||||
|
to the upstream repository.
|
||||||
|
|
||||||
|
### Publish Channel
|
||||||
|
|
||||||
|
This step will update the upstream release branch.
|
||||||
|
|
||||||
|
### Verify Release
|
||||||
|
|
||||||
|
For the final step, the user must manually verify that packaging builds have
|
||||||
|
finished successfully.
|
||||||
|
@ -13,6 +13,7 @@ import 'package:conductor/candidates.dart';
|
|||||||
import 'package:conductor/clean.dart';
|
import 'package:conductor/clean.dart';
|
||||||
import 'package:conductor/codesign.dart';
|
import 'package:conductor/codesign.dart';
|
||||||
import 'package:conductor/globals.dart';
|
import 'package:conductor/globals.dart';
|
||||||
|
import 'package:conductor/next.dart';
|
||||||
import 'package:conductor/repository.dart';
|
import 'package:conductor/repository.dart';
|
||||||
import 'package:conductor/roll_dev.dart';
|
import 'package:conductor/roll_dev.dart';
|
||||||
import 'package:conductor/start.dart';
|
import 'package:conductor/start.dart';
|
||||||
@ -74,6 +75,9 @@ Future<void> main(List<String> args) async {
|
|||||||
checkouts: checkouts,
|
checkouts: checkouts,
|
||||||
flutterRoot: localFlutterRoot,
|
flutterRoot: localFlutterRoot,
|
||||||
),
|
),
|
||||||
|
NextCommand(
|
||||||
|
checkouts: checkouts,
|
||||||
|
),
|
||||||
].forEach(runner.addCommand);
|
].forEach(runner.addCommand);
|
||||||
|
|
||||||
if (!assertsEnabled()) {
|
if (!assertsEnabled()) {
|
||||||
|
@ -115,8 +115,7 @@ class CodesignCommand extends Command<void> {
|
|||||||
revision = (processManager.runSync(
|
revision = (processManager.runSync(
|
||||||
<String>['git', 'rev-parse', 'HEAD'],
|
<String>['git', 'rev-parse', 'HEAD'],
|
||||||
workingDirectory: framework.checkoutDirectory.path,
|
workingDirectory: framework.checkoutDirectory.path,
|
||||||
).stdout as String)
|
).stdout as String).trim();
|
||||||
.trim();
|
|
||||||
assert(revision.isNotEmpty);
|
assert(revision.isNotEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -291,7 +290,7 @@ class CodesignCommand extends Command<void> {
|
|||||||
if (wrongEntitlementBinaries.isNotEmpty) {
|
if (wrongEntitlementBinaries.isNotEmpty) {
|
||||||
stdio.printError(
|
stdio.printError(
|
||||||
'Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:');
|
'Found ${wrongEntitlementBinaries.length} binaries with unexpected entitlements:');
|
||||||
wrongEntitlementBinaries.forEach(print);
|
wrongEntitlementBinaries.forEach(stdio.printError);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unexpectedBinaries.isNotEmpty) {
|
if (unexpectedBinaries.isNotEmpty) {
|
||||||
|
@ -20,6 +20,8 @@ const List<String> kReleaseChannels = <String>[
|
|||||||
|
|
||||||
const String kReleaseDocumentationUrl = 'https://github.com/flutter/flutter/wiki/Flutter-Cherrypick-Process';
|
const String kReleaseDocumentationUrl = 'https://github.com/flutter/flutter/wiki/Flutter-Cherrypick-Process';
|
||||||
|
|
||||||
|
const String kLuciPackagingConsoleLink = 'https://ci.chromium.org/p/flutter/g/packaging/console';
|
||||||
|
|
||||||
final RegExp releaseCandidateBranchRegex = RegExp(
|
final RegExp releaseCandidateBranchRegex = RegExp(
|
||||||
r'flutter-(\d+)\.(\d+)-candidate\.(\d+)',
|
r'flutter-(\d+)\.(\d+)-candidate\.(\d+)',
|
||||||
);
|
);
|
||||||
|
288
dev/conductor/lib/next.dart
Normal file
288
dev/conductor/lib/next.dart
Normal file
@ -0,0 +1,288 @@
|
|||||||
|
// Copyright 2014 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.8
|
||||||
|
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:file/file.dart' show File;
|
||||||
|
import 'package:meta/meta.dart' show required, visibleForTesting;
|
||||||
|
import './globals.dart';
|
||||||
|
import './proto/conductor_state.pb.dart' as pb;
|
||||||
|
import './proto/conductor_state.pbenum.dart';
|
||||||
|
import './repository.dart';
|
||||||
|
import './state.dart';
|
||||||
|
import './stdio.dart';
|
||||||
|
|
||||||
|
const String kStateOption = 'state-file';
|
||||||
|
const String kYesFlag = 'yes';
|
||||||
|
const String kForceFlag = 'force';
|
||||||
|
|
||||||
|
/// Command to proceed from one [pb.ReleasePhase] to the next.
|
||||||
|
class NextCommand extends Command<void> {
|
||||||
|
NextCommand({
|
||||||
|
@required this.checkouts,
|
||||||
|
}) {
|
||||||
|
final String defaultPath = defaultStateFilePath(checkouts.platform);
|
||||||
|
argParser.addOption(
|
||||||
|
kStateOption,
|
||||||
|
defaultsTo: defaultPath,
|
||||||
|
help: 'Path to persistent state file. Defaults to $defaultPath',
|
||||||
|
);
|
||||||
|
argParser.addFlag(
|
||||||
|
kYesFlag,
|
||||||
|
help: 'Auto-accept any confirmation prompts.',
|
||||||
|
hide: true, // primarily for integration testing
|
||||||
|
);
|
||||||
|
argParser.addFlag(
|
||||||
|
kForceFlag,
|
||||||
|
help: 'Force push when updating remote git branches.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final Checkouts checkouts;
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get name => 'next';
|
||||||
|
|
||||||
|
@override
|
||||||
|
String get description => 'Proceed to the next release phase.';
|
||||||
|
|
||||||
|
@override
|
||||||
|
void run() {
|
||||||
|
runNext(
|
||||||
|
autoAccept: argResults[kYesFlag] as bool,
|
||||||
|
checkouts: checkouts,
|
||||||
|
force: argResults[kForceFlag] as bool,
|
||||||
|
stateFile: checkouts.fileSystem.file(argResults[kStateOption]),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
bool prompt(String message, Stdio stdio) {
|
||||||
|
stdio.write('${message.trim()} (y/n) ');
|
||||||
|
final String response = stdio.readLineSync().trim();
|
||||||
|
final String firstChar = response[0].toUpperCase();
|
||||||
|
if (firstChar == 'Y') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (firstChar == 'N') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
throw ConductorException(
|
||||||
|
'Unknown user input (expected "y" or "n"): $response',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@visibleForTesting
|
||||||
|
void runNext({
|
||||||
|
@required bool autoAccept,
|
||||||
|
@required bool force,
|
||||||
|
@required Checkouts checkouts,
|
||||||
|
@required File stateFile,
|
||||||
|
}) {
|
||||||
|
final Stdio stdio = checkouts.stdio;
|
||||||
|
const List<CherrypickState> finishedStates = <CherrypickState>[
|
||||||
|
CherrypickState.COMPLETED,
|
||||||
|
CherrypickState.ABANDONED,
|
||||||
|
];
|
||||||
|
if (!stateFile.existsSync()) {
|
||||||
|
throw ConductorException(
|
||||||
|
'No persistent state file found at ${stateFile.path}.',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
final pb.ConductorState state = readStateFromFile(stateFile);
|
||||||
|
|
||||||
|
switch (state.currentPhase) {
|
||||||
|
case pb.ReleasePhase.APPLY_ENGINE_CHERRYPICKS:
|
||||||
|
final List<pb.Cherrypick> unappliedCherrypicks = <pb.Cherrypick>[];
|
||||||
|
for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) {
|
||||||
|
if (!finishedStates.contains(cherrypick.state)) {
|
||||||
|
unappliedCherrypicks.add(cherrypick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.engine.cherrypicks.isEmpty) {
|
||||||
|
stdio.printStatus('This release has no engine cherrypicks.');
|
||||||
|
break;
|
||||||
|
} else if (unappliedCherrypicks.isEmpty) {
|
||||||
|
stdio.printStatus('All engine cherrypicks have been auto-applied by '
|
||||||
|
'the conductor.\n');
|
||||||
|
if (autoAccept == false) {
|
||||||
|
final bool response = prompt(
|
||||||
|
'Are you ready to push your changes to the repository '
|
||||||
|
'${state.engine.mirror.url}?',
|
||||||
|
stdio,
|
||||||
|
);
|
||||||
|
if (!response) {
|
||||||
|
stdio.printError('Aborting command.');
|
||||||
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stdio.printStatus(
|
||||||
|
'There were ${unappliedCherrypicks.length} cherrypicks that were not auto-applied.');
|
||||||
|
stdio.printStatus('These must be applied manually in the directory '
|
||||||
|
'${state.engine.checkoutPath} before proceeding.\n');
|
||||||
|
if (autoAccept == false) {
|
||||||
|
final bool response = prompt(
|
||||||
|
'Are you ready to push your engine branch to the repository '
|
||||||
|
'${state.engine.mirror.url}?',
|
||||||
|
stdio,
|
||||||
|
);
|
||||||
|
if (!response) {
|
||||||
|
stdio.printError('Aborting command.');
|
||||||
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case pb.ReleasePhase.CODESIGN_ENGINE_BINARIES:
|
||||||
|
if (autoAccept == false) {
|
||||||
|
// TODO(fujino): actually test if binaries have been codesigned on macOS
|
||||||
|
final bool response = prompt(
|
||||||
|
'Has CI passed for the engine PR and binaries been codesigned?',
|
||||||
|
stdio,
|
||||||
|
);
|
||||||
|
if (!response) {
|
||||||
|
stdio.printError('Aborting command.');
|
||||||
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case pb.ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS:
|
||||||
|
final List<pb.Cherrypick> unappliedCherrypicks = <pb.Cherrypick>[];
|
||||||
|
for (final pb.Cherrypick cherrypick in state.framework.cherrypicks) {
|
||||||
|
if (!finishedStates.contains(cherrypick.state)) {
|
||||||
|
unappliedCherrypicks.add(cherrypick);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (state.framework.cherrypicks.isEmpty) {
|
||||||
|
stdio.printStatus('This release has no framework cherrypicks.');
|
||||||
|
break;
|
||||||
|
} else if (unappliedCherrypicks.isEmpty) {
|
||||||
|
stdio.printStatus('All framework cherrypicks have been auto-applied by '
|
||||||
|
'the conductor.\n');
|
||||||
|
if (autoAccept == false) {
|
||||||
|
final bool response = prompt(
|
||||||
|
'Are you ready to push your changes to the repository '
|
||||||
|
'${state.framework.mirror.url}?',
|
||||||
|
stdio,
|
||||||
|
);
|
||||||
|
if (!response) {
|
||||||
|
stdio.printError('Aborting command.');
|
||||||
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
stdio.printStatus(
|
||||||
|
'There were ${unappliedCherrypicks.length} cherrypicks that were not auto-applied.');
|
||||||
|
stdio.printStatus('These must be applied manually in the directory '
|
||||||
|
'${state.framework.checkoutPath} before proceeding.\n');
|
||||||
|
if (autoAccept == false) {
|
||||||
|
final bool response = prompt(
|
||||||
|
'Are you ready to push your framework branch to the repository '
|
||||||
|
'${state.framework.mirror.url}?',
|
||||||
|
stdio,
|
||||||
|
);
|
||||||
|
if (!response) {
|
||||||
|
stdio.printError('Aborting command.');
|
||||||
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
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 upstream = Remote(
|
||||||
|
name: RemoteName.upstream,
|
||||||
|
url: state.framework.upstream.url,
|
||||||
|
);
|
||||||
|
final FrameworkRepository framework = FrameworkRepository(
|
||||||
|
checkouts,
|
||||||
|
initialRef: state.framework.candidateBranch,
|
||||||
|
upstreamRemote: upstream,
|
||||||
|
previousCheckoutLocation: state.framework.checkoutPath,
|
||||||
|
);
|
||||||
|
final String headRevision = framework.reverseParse('HEAD');
|
||||||
|
if (autoAccept == false) {
|
||||||
|
final bool response = prompt(
|
||||||
|
'Has CI passed for the framework PR?',
|
||||||
|
stdio,
|
||||||
|
);
|
||||||
|
if (!response) {
|
||||||
|
stdio.printError('Aborting command.');
|
||||||
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
framework.tag(headRevision, state.releaseVersion, upstream.name);
|
||||||
|
break;
|
||||||
|
case pb.ReleasePhase.PUBLISH_CHANNEL:
|
||||||
|
final Remote upstream = Remote(
|
||||||
|
name: RemoteName.upstream,
|
||||||
|
url: state.framework.upstream.url,
|
||||||
|
);
|
||||||
|
final FrameworkRepository framework = FrameworkRepository(
|
||||||
|
checkouts,
|
||||||
|
initialRef: state.framework.candidateBranch,
|
||||||
|
upstreamRemote: upstream,
|
||||||
|
previousCheckoutLocation: state.framework.checkoutPath,
|
||||||
|
);
|
||||||
|
final String headRevision = framework.reverseParse('HEAD');
|
||||||
|
if (autoAccept == false) {
|
||||||
|
final bool response = prompt(
|
||||||
|
'Are you ready to publish release ${state.releaseVersion} to '
|
||||||
|
'channel ${state.releaseChannel} at ${state.framework.upstream.url}?',
|
||||||
|
stdio,
|
||||||
|
);
|
||||||
|
if (!response) {
|
||||||
|
stdio.printError('Aborting command.');
|
||||||
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
framework.updateChannel(
|
||||||
|
headRevision,
|
||||||
|
state.framework.upstream.url,
|
||||||
|
state.releaseChannel,
|
||||||
|
force: force,
|
||||||
|
);
|
||||||
|
break;
|
||||||
|
case pb.ReleasePhase.VERIFY_RELEASE:
|
||||||
|
stdio.printStatus(
|
||||||
|
'The current status of packaging builds can be seen at:\n'
|
||||||
|
'\t$kLuciPackagingConsoleLink',
|
||||||
|
);
|
||||||
|
if (autoAccept == false) {
|
||||||
|
final bool response = prompt(
|
||||||
|
'Have all packaging builds finished successfully?',
|
||||||
|
stdio,
|
||||||
|
);
|
||||||
|
if (!response) {
|
||||||
|
stdio.printError('Aborting command.');
|
||||||
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case pb.ReleasePhase.RELEASE_COMPLETED:
|
||||||
|
throw ConductorException('This release is finished.');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
final ReleasePhase nextPhase = getNextPhase(state.currentPhase);
|
||||||
|
stdio.printStatus('\nUpdating phase from ${state.currentPhase} to $nextPhase...\n');
|
||||||
|
state.currentPhase = nextPhase;
|
||||||
|
stdio.printStatus(phaseInstructions(state));
|
||||||
|
|
||||||
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
}
|
@ -32,7 +32,7 @@ for SOURCE_FILE in $(ls "$DIR"/*.pb*.dart); do
|
|||||||
"$DARTFMT" --overwrite --line-length 120 "$SOURCE_FILE"
|
"$DARTFMT" --overwrite --line-length 120 "$SOURCE_FILE"
|
||||||
|
|
||||||
# Create temp copy with the license header prepended
|
# Create temp copy with the license header prepended
|
||||||
cp license_header.txt "${SOURCE_FILE}.tmp"
|
cp "$DIR/license_header.txt" "${SOURCE_FILE}.tmp"
|
||||||
|
|
||||||
# Add an extra newline required by analysis (analysis also prevents
|
# Add an extra newline required by analysis (analysis also prevents
|
||||||
# license_header.txt from having the trailing newline)
|
# license_header.txt from having the trailing newline)
|
||||||
|
@ -378,12 +378,13 @@ class ConductorState extends $pb.GeneratedMessage {
|
|||||||
protoName: 'lastUpdatedDate')
|
protoName: 'lastUpdatedDate')
|
||||||
..pPS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'logs')
|
..pPS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'logs')
|
||||||
..e<ReleasePhase>(
|
..e<ReleasePhase>(
|
||||||
9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'lastPhase', $pb.PbFieldType.OE,
|
9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'currentPhase', $pb.PbFieldType.OE,
|
||||||
protoName: 'lastPhase',
|
protoName: 'currentPhase',
|
||||||
defaultOrMaker: ReleasePhase.INITIALIZE,
|
defaultOrMaker: ReleasePhase.APPLY_ENGINE_CHERRYPICKS,
|
||||||
valueOf: ReleasePhase.valueOf,
|
valueOf: ReleasePhase.valueOf,
|
||||||
enumValues: ReleasePhase.values)
|
enumValues: ReleasePhase.values)
|
||||||
..aOS(10, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'conductorVersion')
|
..aOS(10, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'conductorVersion',
|
||||||
|
protoName: 'conductorVersion')
|
||||||
..hasRequiredFields = false;
|
..hasRequiredFields = false;
|
||||||
|
|
||||||
ConductorState._() : super();
|
ConductorState._() : super();
|
||||||
@ -395,7 +396,7 @@ class ConductorState extends $pb.GeneratedMessage {
|
|||||||
$fixnum.Int64 createdDate,
|
$fixnum.Int64 createdDate,
|
||||||
$fixnum.Int64 lastUpdatedDate,
|
$fixnum.Int64 lastUpdatedDate,
|
||||||
$core.Iterable<$core.String> logs,
|
$core.Iterable<$core.String> logs,
|
||||||
ReleasePhase lastPhase,
|
ReleasePhase currentPhase,
|
||||||
$core.String conductorVersion,
|
$core.String conductorVersion,
|
||||||
}) {
|
}) {
|
||||||
final _result = create();
|
final _result = create();
|
||||||
@ -420,8 +421,8 @@ class ConductorState extends $pb.GeneratedMessage {
|
|||||||
if (logs != null) {
|
if (logs != null) {
|
||||||
_result.logs.addAll(logs);
|
_result.logs.addAll(logs);
|
||||||
}
|
}
|
||||||
if (lastPhase != null) {
|
if (currentPhase != null) {
|
||||||
_result.lastPhase = lastPhase;
|
_result.currentPhase = currentPhase;
|
||||||
}
|
}
|
||||||
if (conductorVersion != null) {
|
if (conductorVersion != null) {
|
||||||
_result.conductorVersion = conductorVersion;
|
_result.conductorVersion = conductorVersion;
|
||||||
@ -531,16 +532,16 @@ class ConductorState extends $pb.GeneratedMessage {
|
|||||||
$core.List<$core.String> get logs => $_getList(6);
|
$core.List<$core.String> get logs => $_getList(6);
|
||||||
|
|
||||||
@$pb.TagNumber(9)
|
@$pb.TagNumber(9)
|
||||||
ReleasePhase get lastPhase => $_getN(7);
|
ReleasePhase get currentPhase => $_getN(7);
|
||||||
@$pb.TagNumber(9)
|
@$pb.TagNumber(9)
|
||||||
set lastPhase(ReleasePhase v) {
|
set currentPhase(ReleasePhase v) {
|
||||||
setField(9, v);
|
setField(9, v);
|
||||||
}
|
}
|
||||||
|
|
||||||
@$pb.TagNumber(9)
|
@$pb.TagNumber(9)
|
||||||
$core.bool hasLastPhase() => $_has(7);
|
$core.bool hasCurrentPhase() => $_has(7);
|
||||||
@$pb.TagNumber(9)
|
@$pb.TagNumber(9)
|
||||||
void clearLastPhase() => clearField(9);
|
void clearCurrentPhase() => clearField(9);
|
||||||
|
|
||||||
@$pb.TagNumber(10)
|
@$pb.TagNumber(10)
|
||||||
$core.String get conductorVersion => $_getSZ(8);
|
$core.String get conductorVersion => $_getSZ(8);
|
||||||
|
@ -14,29 +14,29 @@ import 'dart:core' as $core;
|
|||||||
import 'package:protobuf/protobuf.dart' as $pb;
|
import 'package:protobuf/protobuf.dart' as $pb;
|
||||||
|
|
||||||
class ReleasePhase extends $pb.ProtobufEnum {
|
class ReleasePhase extends $pb.ProtobufEnum {
|
||||||
static const ReleasePhase INITIALIZE =
|
|
||||||
ReleasePhase._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'INITIALIZE');
|
|
||||||
static const ReleasePhase APPLY_ENGINE_CHERRYPICKS =
|
static const ReleasePhase APPLY_ENGINE_CHERRYPICKS =
|
||||||
ReleasePhase._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'APPLY_ENGINE_CHERRYPICKS');
|
ReleasePhase._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'APPLY_ENGINE_CHERRYPICKS');
|
||||||
static const ReleasePhase CODESIGN_ENGINE_BINARIES =
|
static const ReleasePhase CODESIGN_ENGINE_BINARIES =
|
||||||
ReleasePhase._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CODESIGN_ENGINE_BINARIES');
|
ReleasePhase._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CODESIGN_ENGINE_BINARIES');
|
||||||
static const ReleasePhase APPLY_FRAMEWORK_CHERRYPICKS = ReleasePhase._(
|
static const ReleasePhase APPLY_FRAMEWORK_CHERRYPICKS = ReleasePhase._(
|
||||||
3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'APPLY_FRAMEWORK_CHERRYPICKS');
|
2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'APPLY_FRAMEWORK_CHERRYPICKS');
|
||||||
static const ReleasePhase PUBLISH_VERSION =
|
static const ReleasePhase PUBLISH_VERSION =
|
||||||
ReleasePhase._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PUBLISH_VERSION');
|
ReleasePhase._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PUBLISH_VERSION');
|
||||||
static const ReleasePhase PUBLISH_CHANNEL =
|
static const ReleasePhase PUBLISH_CHANNEL =
|
||||||
ReleasePhase._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PUBLISH_CHANNEL');
|
ReleasePhase._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PUBLISH_CHANNEL');
|
||||||
static const ReleasePhase VERIFY_RELEASE =
|
static const ReleasePhase VERIFY_RELEASE =
|
||||||
ReleasePhase._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : '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');
|
||||||
|
|
||||||
static const $core.List<ReleasePhase> values = <ReleasePhase>[
|
static const $core.List<ReleasePhase> values = <ReleasePhase>[
|
||||||
INITIALIZE,
|
|
||||||
APPLY_ENGINE_CHERRYPICKS,
|
APPLY_ENGINE_CHERRYPICKS,
|
||||||
CODESIGN_ENGINE_BINARIES,
|
CODESIGN_ENGINE_BINARIES,
|
||||||
APPLY_FRAMEWORK_CHERRYPICKS,
|
APPLY_FRAMEWORK_CHERRYPICKS,
|
||||||
PUBLISH_VERSION,
|
PUBLISH_VERSION,
|
||||||
PUBLISH_CHANNEL,
|
PUBLISH_CHANNEL,
|
||||||
VERIFY_RELEASE,
|
VERIFY_RELEASE,
|
||||||
|
RELEASE_COMPLETED,
|
||||||
];
|
];
|
||||||
|
|
||||||
static final $core.Map<$core.int, ReleasePhase> _byValue = $pb.ProtobufEnum.initByValue(values);
|
static final $core.Map<$core.int, ReleasePhase> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||||
|
@ -17,19 +17,19 @@ import 'dart:typed_data' as $typed_data;
|
|||||||
const ReleasePhase$json = const {
|
const ReleasePhase$json = const {
|
||||||
'1': 'ReleasePhase',
|
'1': 'ReleasePhase',
|
||||||
'2': const [
|
'2': const [
|
||||||
const {'1': 'INITIALIZE', '2': 0},
|
const {'1': 'APPLY_ENGINE_CHERRYPICKS', '2': 0},
|
||||||
const {'1': 'APPLY_ENGINE_CHERRYPICKS', '2': 1},
|
const {'1': 'CODESIGN_ENGINE_BINARIES', '2': 1},
|
||||||
const {'1': 'CODESIGN_ENGINE_BINARIES', '2': 2},
|
const {'1': 'APPLY_FRAMEWORK_CHERRYPICKS', '2': 2},
|
||||||
const {'1': 'APPLY_FRAMEWORK_CHERRYPICKS', '2': 3},
|
const {'1': 'PUBLISH_VERSION', '2': 3},
|
||||||
const {'1': 'PUBLISH_VERSION', '2': 4},
|
const {'1': 'PUBLISH_CHANNEL', '2': 4},
|
||||||
const {'1': 'PUBLISH_CHANNEL', '2': 5},
|
const {'1': 'VERIFY_RELEASE', '2': 5},
|
||||||
const {'1': 'VERIFY_RELEASE', '2': 6},
|
const {'1': 'RELEASE_COMPLETED', '2': 6},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `ReleasePhase`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
/// Descriptor for `ReleasePhase`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||||
final $typed_data.Uint8List releasePhaseDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List releasePhaseDescriptor = $convert.base64Decode(
|
||||||
'CgxSZWxlYXNlUGhhc2USDgoKSU5JVElBTElaRRAAEhwKGEFQUExZX0VOR0lORV9DSEVSUllQSUNLUxABEhwKGENPREVTSUdOX0VOR0lORV9CSU5BUklFUxACEh8KG0FQUExZX0ZSQU1FV09SS19DSEVSUllQSUNLUxADEhMKD1BVQkxJU0hfVkVSU0lPThAEEhMKD1BVQkxJU0hfQ0hBTk5FTBAFEhIKDlZFUklGWV9SRUxFQVNFEAY=');
|
'CgxSZWxlYXNlUGhhc2USHAoYQVBQTFlfRU5HSU5FX0NIRVJSWVBJQ0tTEAASHAoYQ09ERVNJR05fRU5HSU5FX0JJTkFSSUVTEAESHwobQVBQTFlfRlJBTUVXT1JLX0NIRVJSWVBJQ0tTEAISEwoPUFVCTElTSF9WRVJTSU9OEAMSEwoPUFVCTElTSF9DSEFOTkVMEAQSEgoOVkVSSUZZX1JFTEVBU0UQBRIVChFSRUxFQVNFX0NPTVBMRVRFRBAG');
|
||||||
@$core.Deprecated('Use cherrypickStateDescriptor instead')
|
@$core.Deprecated('Use cherrypickStateDescriptor instead')
|
||||||
const CherrypickState$json = const {
|
const CherrypickState$json = const {
|
||||||
'1': 'CherrypickState',
|
'1': 'CherrypickState',
|
||||||
@ -98,11 +98,11 @@ const ConductorState$json = const {
|
|||||||
const {'1': 'createdDate', '3': 6, '4': 1, '5': 3, '10': 'createdDate'},
|
const {'1': 'createdDate', '3': 6, '4': 1, '5': 3, '10': 'createdDate'},
|
||||||
const {'1': 'lastUpdatedDate', '3': 7, '4': 1, '5': 3, '10': 'lastUpdatedDate'},
|
const {'1': 'lastUpdatedDate', '3': 7, '4': 1, '5': 3, '10': 'lastUpdatedDate'},
|
||||||
const {'1': 'logs', '3': 8, '4': 3, '5': 9, '10': 'logs'},
|
const {'1': 'logs', '3': 8, '4': 3, '5': 9, '10': 'logs'},
|
||||||
const {'1': 'lastPhase', '3': 9, '4': 1, '5': 14, '6': '.conductor_state.ReleasePhase', '10': 'lastPhase'},
|
const {'1': 'currentPhase', '3': 9, '4': 1, '5': 14, '6': '.conductor_state.ReleasePhase', '10': 'currentPhase'},
|
||||||
const {'1': 'conductor_version', '3': 10, '4': 1, '5': 9, '10': 'conductorVersion'},
|
const {'1': 'conductorVersion', '3': 10, '4': 1, '5': 9, '10': 'conductorVersion'},
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Descriptor for `ConductorState`. Decode as a `google.protobuf.DescriptorProto`.
|
/// Descriptor for `ConductorState`. Decode as a `google.protobuf.DescriptorProto`.
|
||||||
final $typed_data.Uint8List conductorStateDescriptor = $convert.base64Decode(
|
final $typed_data.Uint8List conductorStateDescriptor = $convert.base64Decode(
|
||||||
'Cg5Db25kdWN0b3JTdGF0ZRImCg5yZWxlYXNlQ2hhbm5lbBgBIAEoCVIOcmVsZWFzZUNoYW5uZWwSJgoOcmVsZWFzZVZlcnNpb24YAiABKAlSDnJlbGVhc2VWZXJzaW9uEjMKBmVuZ2luZRgEIAEoCzIbLmNvbmR1Y3Rvcl9zdGF0ZS5SZXBvc2l0b3J5UgZlbmdpbmUSOQoJZnJhbWV3b3JrGAUgASgLMhsuY29uZHVjdG9yX3N0YXRlLlJlcG9zaXRvcnlSCWZyYW1ld29yaxIgCgtjcmVhdGVkRGF0ZRgGIAEoA1ILY3JlYXRlZERhdGUSKAoPbGFzdFVwZGF0ZWREYXRlGAcgASgDUg9sYXN0VXBkYXRlZERhdGUSEgoEbG9ncxgIIAMoCVIEbG9ncxI7CglsYXN0UGhhc2UYCSABKA4yHS5jb25kdWN0b3Jfc3RhdGUuUmVsZWFzZVBoYXNlUglsYXN0UGhhc2USKwoRY29uZHVjdG9yX3ZlcnNpb24YCiABKAlSEGNvbmR1Y3RvclZlcnNpb24=');
|
'Cg5Db25kdWN0b3JTdGF0ZRImCg5yZWxlYXNlQ2hhbm5lbBgBIAEoCVIOcmVsZWFzZUNoYW5uZWwSJgoOcmVsZWFzZVZlcnNpb24YAiABKAlSDnJlbGVhc2VWZXJzaW9uEjMKBmVuZ2luZRgEIAEoCzIbLmNvbmR1Y3Rvcl9zdGF0ZS5SZXBvc2l0b3J5UgZlbmdpbmUSOQoJZnJhbWV3b3JrGAUgASgLMhsuY29uZHVjdG9yX3N0YXRlLlJlcG9zaXRvcnlSCWZyYW1ld29yaxIgCgtjcmVhdGVkRGF0ZRgGIAEoA1ILY3JlYXRlZERhdGUSKAoPbGFzdFVwZGF0ZWREYXRlGAcgASgDUg9sYXN0VXBkYXRlZERhdGUSEgoEbG9ncxgIIAMoCVIEbG9ncxJBCgxjdXJyZW50UGhhc2UYCSABKA4yHS5jb25kdWN0b3Jfc3RhdGUuUmVsZWFzZVBoYXNlUgxjdXJyZW50UGhhc2USKgoQY29uZHVjdG9yVmVyc2lvbhgKIAEoCVIQY29uZHVjdG9yVmVyc2lvbg==');
|
||||||
|
@ -10,21 +10,23 @@ message Remote {
|
|||||||
|
|
||||||
enum ReleasePhase {
|
enum ReleasePhase {
|
||||||
// Release was started with `conductor start` and repositories cloned.
|
// Release was started with `conductor start` and repositories cloned.
|
||||||
INITIALIZE = 0;
|
APPLY_ENGINE_CHERRYPICKS = 0;
|
||||||
APPLY_ENGINE_CHERRYPICKS = 1;
|
CODESIGN_ENGINE_BINARIES = 1;
|
||||||
CODESIGN_ENGINE_BINARIES = 2;
|
APPLY_FRAMEWORK_CHERRYPICKS = 2;
|
||||||
APPLY_FRAMEWORK_CHERRYPICKS = 3;
|
|
||||||
|
|
||||||
// Git tag applied to framework RC branch HEAD and pushed upstream.
|
// Git tag applied to framework RC branch HEAD and pushed upstream.
|
||||||
PUBLISH_VERSION = 4;
|
PUBLISH_VERSION = 3;
|
||||||
|
|
||||||
// RC branch HEAD pushed to upstream release branch.
|
// RC branch HEAD pushed to upstream release branch.
|
||||||
//
|
//
|
||||||
// For example, flutter-1.2-candidate.3 -> upstream/beta
|
// For example, flutter-1.2-candidate.3 -> upstream/beta
|
||||||
PUBLISH_CHANNEL = 5;
|
PUBLISH_CHANNEL = 4;
|
||||||
|
|
||||||
// Package artifacts verified to exist on cloud storage.
|
// Package artifacts verified to exist on cloud storage.
|
||||||
VERIFY_RELEASE = 6;
|
VERIFY_RELEASE = 5;
|
||||||
|
|
||||||
|
// There is no further work to be done.
|
||||||
|
RELEASE_COMPLETED = 6;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum CherrypickState {
|
enum CherrypickState {
|
||||||
@ -98,9 +100,9 @@ message ConductorState {
|
|||||||
|
|
||||||
repeated string logs = 8;
|
repeated string logs = 8;
|
||||||
|
|
||||||
// The last [ReleasePhase] that was successfully completed.
|
// The current [ReleasePhase] that has yet to be completed.
|
||||||
ReleasePhase lastPhase = 9;
|
ReleasePhase currentPhase = 9;
|
||||||
|
|
||||||
// Commit hash of the Conductor tool.
|
// Commit hash of the Conductor tool.
|
||||||
string conductor_version = 10;
|
string conductorVersion = 10;
|
||||||
}
|
}
|
||||||
|
@ -25,7 +25,7 @@ class Remote {
|
|||||||
const Remote({
|
const Remote({
|
||||||
required RemoteName name,
|
required RemoteName name,
|
||||||
required this.url,
|
required this.url,
|
||||||
}) : _name = name;
|
}) : _name = name, assert(url != null), assert (url != '');
|
||||||
|
|
||||||
final RemoteName _name;
|
final RemoteName _name;
|
||||||
|
|
||||||
@ -47,7 +47,7 @@ class Remote {
|
|||||||
abstract class Repository {
|
abstract class Repository {
|
||||||
Repository({
|
Repository({
|
||||||
required this.name,
|
required this.name,
|
||||||
required this.fetchRemote,
|
required this.upstreamRemote,
|
||||||
required this.processManager,
|
required this.processManager,
|
||||||
required this.stdio,
|
required this.stdio,
|
||||||
required this.platform,
|
required this.platform,
|
||||||
@ -55,20 +55,34 @@ abstract class Repository {
|
|||||||
required this.parentDirectory,
|
required this.parentDirectory,
|
||||||
this.initialRef,
|
this.initialRef,
|
||||||
this.localUpstream = false,
|
this.localUpstream = false,
|
||||||
this.useExistingCheckout = false,
|
String? previousCheckoutLocation,
|
||||||
this.pushRemote,
|
this.mirrorRemote,
|
||||||
}) : git = Git(processManager),
|
}) : git = Git(processManager),
|
||||||
assert(localUpstream != null),
|
assert(localUpstream != null),
|
||||||
assert(useExistingCheckout != null);
|
assert(upstreamRemote.url.isNotEmpty) {
|
||||||
|
if (previousCheckoutLocation != null) {
|
||||||
|
_checkoutDirectory = fileSystem.directory(previousCheckoutLocation);
|
||||||
|
if (!_checkoutDirectory!.existsSync()) {
|
||||||
|
throw ConductorException('Provided previousCheckoutLocation $previousCheckoutLocation does not exist on disk!');
|
||||||
|
}
|
||||||
|
if (initialRef != null) {
|
||||||
|
git.run(
|
||||||
|
<String>['checkout', '${upstreamRemote.name}/$initialRef'],
|
||||||
|
'Checking out initialRef $initialRef',
|
||||||
|
workingDirectory: _checkoutDirectory!.path,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
final String name;
|
final String name;
|
||||||
final Remote fetchRemote;
|
final Remote upstreamRemote;
|
||||||
|
|
||||||
/// Remote to publish tags and commits to.
|
/// Remote for user's mirror.
|
||||||
///
|
///
|
||||||
/// This value can be null, in which case attempting to publish will lead to
|
/// This value can be null, in which case attempting to access it will lead to
|
||||||
/// a [ConductorException].
|
/// a [ConductorException].
|
||||||
final Remote? pushRemote;
|
final Remote? mirrorRemote;
|
||||||
|
|
||||||
/// The initial ref (branch or commit name) to check out.
|
/// The initial ref (branch or commit name) to check out.
|
||||||
final String? initialRef;
|
final String? initialRef;
|
||||||
@ -78,7 +92,6 @@ abstract class Repository {
|
|||||||
final Platform platform;
|
final Platform platform;
|
||||||
final FileSystem fileSystem;
|
final FileSystem fileSystem;
|
||||||
final Directory parentDirectory;
|
final Directory parentDirectory;
|
||||||
final bool useExistingCheckout;
|
|
||||||
|
|
||||||
/// If the repository will be used as an upstream for a test repo.
|
/// If the repository will be used as an upstream for a test repo.
|
||||||
final bool localUpstream;
|
final bool localUpstream;
|
||||||
@ -100,36 +113,35 @@ abstract class Repository {
|
|||||||
|
|
||||||
/// Ensure the repository is cloned to disk and initialized with proper state.
|
/// Ensure the repository is cloned to disk and initialized with proper state.
|
||||||
void lazilyInitialize(Directory checkoutDirectory) {
|
void lazilyInitialize(Directory checkoutDirectory) {
|
||||||
if (!useExistingCheckout && checkoutDirectory.existsSync()) {
|
if (checkoutDirectory.existsSync()) {
|
||||||
stdio.printTrace('Deleting $name from ${checkoutDirectory.path}...');
|
stdio.printTrace('Deleting $name from ${checkoutDirectory.path}...');
|
||||||
checkoutDirectory.deleteSync(recursive: true);
|
checkoutDirectory.deleteSync(recursive: true);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!checkoutDirectory.existsSync()) {
|
|
||||||
stdio.printTrace(
|
stdio.printTrace(
|
||||||
'Cloning $name from ${fetchRemote.url} to ${checkoutDirectory.path}...',
|
'Cloning $name from ${upstreamRemote.url} to ${checkoutDirectory.path}...',
|
||||||
);
|
);
|
||||||
git.run(
|
git.run(
|
||||||
<String>[
|
<String>[
|
||||||
'clone',
|
'clone',
|
||||||
'--origin',
|
'--origin',
|
||||||
fetchRemote.name,
|
upstreamRemote.name,
|
||||||
'--',
|
'--',
|
||||||
fetchRemote.url,
|
upstreamRemote.url,
|
||||||
checkoutDirectory.path
|
checkoutDirectory.path
|
||||||
],
|
],
|
||||||
'Cloning $name repo',
|
'Cloning $name repo',
|
||||||
workingDirectory: parentDirectory.path,
|
workingDirectory: parentDirectory.path,
|
||||||
);
|
);
|
||||||
if (pushRemote != null) {
|
if (mirrorRemote != null) {
|
||||||
git.run(
|
git.run(
|
||||||
<String>['remote', 'add', pushRemote!.name, pushRemote!.url],
|
<String>['remote', 'add', mirrorRemote!.name, mirrorRemote!.url],
|
||||||
'Adding remote ${pushRemote!.url} as ${pushRemote!.name}',
|
'Adding remote ${mirrorRemote!.url} as ${mirrorRemote!.name}',
|
||||||
workingDirectory: checkoutDirectory.path,
|
workingDirectory: checkoutDirectory.path,
|
||||||
);
|
);
|
||||||
git.run(
|
git.run(
|
||||||
<String>['fetch', pushRemote!.name],
|
<String>['fetch', mirrorRemote!.name],
|
||||||
'Fetching git remote ${pushRemote!.name}',
|
'Fetching git remote ${mirrorRemote!.name}',
|
||||||
workingDirectory: checkoutDirectory.path,
|
workingDirectory: checkoutDirectory.path,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -144,11 +156,10 @@ abstract class Repository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
if (initialRef != null) {
|
if (initialRef != null) {
|
||||||
git.run(
|
git.run(
|
||||||
<String>['checkout', '${fetchRemote.name}/$initialRef'],
|
<String>['checkout', '${upstreamRemote.name}/$initialRef'],
|
||||||
'Checking out initialRef $initialRef',
|
'Checking out initialRef $initialRef',
|
||||||
workingDirectory: checkoutDirectory.path,
|
workingDirectory: checkoutDirectory.path,
|
||||||
);
|
);
|
||||||
@ -217,13 +228,25 @@ abstract class Repository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Obtain the version tag of the previous dev release.
|
/// Obtain the version tag at the tip of a release branch.
|
||||||
String getFullTag(String remoteName) {
|
String getFullTag(
|
||||||
const String glob = '*.*.*-*.*.pre';
|
String remoteName,
|
||||||
|
String branchName, {
|
||||||
|
bool exact = true,
|
||||||
|
}) {
|
||||||
|
// includes both stable (e.g. 1.2.3) and dev tags (e.g. 1.2.3-4.5.pre)
|
||||||
|
const String glob = '*.*.*';
|
||||||
// describe the latest dev release
|
// describe the latest dev release
|
||||||
final String ref = 'refs/remotes/$remoteName/dev';
|
final String ref = 'refs/remotes/$remoteName/$branchName';
|
||||||
return git.getOutput(
|
return git.getOutput(
|
||||||
<String>['describe', '--match', glob, '--exact-match', '--tags', ref],
|
<String>[
|
||||||
|
'describe',
|
||||||
|
'--match',
|
||||||
|
glob,
|
||||||
|
if (exact) '--exact-match',
|
||||||
|
'--tags',
|
||||||
|
ref,
|
||||||
|
],
|
||||||
'obtain last released version number',
|
'obtain last released version number',
|
||||||
workingDirectory: checkoutDirectory.path,
|
workingDirectory: checkoutDirectory.path,
|
||||||
);
|
);
|
||||||
@ -235,7 +258,7 @@ abstract class Repository {
|
|||||||
.getOutput(
|
.getOutput(
|
||||||
<String>['rev-list', ...args],
|
<String>['rev-list', ...args],
|
||||||
'rev-list with args ${args.join(' ')}',
|
'rev-list with args ${args.join(' ')}',
|
||||||
workingDirectory: checkoutDirectory.path,
|
workingDirectory: checkoutDirectory.path
|
||||||
)
|
)
|
||||||
.trim()
|
.trim()
|
||||||
.split('\n');
|
.split('\n');
|
||||||
@ -332,20 +355,6 @@ abstract class Repository {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Tag [commit] and push the tag to the remote.
|
|
||||||
void tag(String commit, String tagName, String remote) {
|
|
||||||
git.run(
|
|
||||||
<String>['tag', tagName, commit],
|
|
||||||
'tag the commit with the version label',
|
|
||||||
workingDirectory: checkoutDirectory.path,
|
|
||||||
);
|
|
||||||
git.run(
|
|
||||||
<String>['push', remote, tagName],
|
|
||||||
'publish the tag to the repo',
|
|
||||||
workingDirectory: checkoutDirectory.path,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Push [commit] to the release channel [branch].
|
/// Push [commit] to the release channel [branch].
|
||||||
void updateChannel(
|
void updateChannel(
|
||||||
String commit,
|
String commit,
|
||||||
@ -419,16 +428,16 @@ class FrameworkRepository extends Repository {
|
|||||||
FrameworkRepository(
|
FrameworkRepository(
|
||||||
this.checkouts, {
|
this.checkouts, {
|
||||||
String name = 'framework',
|
String name = 'framework',
|
||||||
Remote fetchRemote = const Remote(
|
Remote upstreamRemote = const Remote(
|
||||||
name: RemoteName.upstream, url: FrameworkRepository.defaultUpstream),
|
name: RemoteName.upstream, url: FrameworkRepository.defaultUpstream),
|
||||||
bool localUpstream = false,
|
bool localUpstream = false,
|
||||||
bool useExistingCheckout = false,
|
String? previousCheckoutLocation,
|
||||||
String? initialRef,
|
String? initialRef,
|
||||||
Remote? pushRemote,
|
Remote? mirrorRemote,
|
||||||
}) : super(
|
}) : super(
|
||||||
name: name,
|
name: name,
|
||||||
fetchRemote: fetchRemote,
|
upstreamRemote: upstreamRemote,
|
||||||
pushRemote: pushRemote,
|
mirrorRemote: mirrorRemote,
|
||||||
initialRef: initialRef,
|
initialRef: initialRef,
|
||||||
fileSystem: checkouts.fileSystem,
|
fileSystem: checkouts.fileSystem,
|
||||||
localUpstream: localUpstream,
|
localUpstream: localUpstream,
|
||||||
@ -436,7 +445,7 @@ class FrameworkRepository extends Repository {
|
|||||||
platform: checkouts.platform,
|
platform: checkouts.platform,
|
||||||
processManager: checkouts.processManager,
|
processManager: checkouts.processManager,
|
||||||
stdio: checkouts.stdio,
|
stdio: checkouts.stdio,
|
||||||
useExistingCheckout: useExistingCheckout,
|
previousCheckoutLocation: previousCheckoutLocation,
|
||||||
);
|
);
|
||||||
|
|
||||||
/// A [FrameworkRepository] with the host conductor's repo set as upstream.
|
/// A [FrameworkRepository] with the host conductor's repo set as upstream.
|
||||||
@ -446,18 +455,18 @@ class FrameworkRepository extends Repository {
|
|||||||
factory FrameworkRepository.localRepoAsUpstream(
|
factory FrameworkRepository.localRepoAsUpstream(
|
||||||
Checkouts checkouts, {
|
Checkouts checkouts, {
|
||||||
String name = 'framework',
|
String name = 'framework',
|
||||||
bool useExistingCheckout = false,
|
String? previousCheckoutLocation,
|
||||||
required String upstreamPath,
|
required String upstreamPath,
|
||||||
}) {
|
}) {
|
||||||
return FrameworkRepository(
|
return FrameworkRepository(
|
||||||
checkouts,
|
checkouts,
|
||||||
name: name,
|
name: name,
|
||||||
fetchRemote: Remote(
|
upstreamRemote: Remote(
|
||||||
name: RemoteName.upstream,
|
name: RemoteName.upstream,
|
||||||
url: 'file://$upstreamPath/',
|
url: 'file://$upstreamPath/',
|
||||||
),
|
),
|
||||||
localUpstream: false,
|
localUpstream: false,
|
||||||
useExistingCheckout: useExistingCheckout,
|
previousCheckoutLocation: previousCheckoutLocation,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -473,6 +482,27 @@ class FrameworkRepository extends Repository {
|
|||||||
'cache',
|
'cache',
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Tag [commit] and push the tag to the remote.
|
||||||
|
void tag(String commit, String tagName, String remote) {
|
||||||
|
assert(commit.isNotEmpty);
|
||||||
|
assert(tagName.isNotEmpty);
|
||||||
|
assert(remote.isNotEmpty);
|
||||||
|
stdio.printStatus('About to tag commit $commit as $tagName...');
|
||||||
|
git.run(
|
||||||
|
<String>['tag', tagName, commit],
|
||||||
|
'tag the commit with the version label',
|
||||||
|
workingDirectory: checkoutDirectory.path,
|
||||||
|
);
|
||||||
|
stdio.printStatus('Tagging successful.');
|
||||||
|
stdio.printStatus('About to push $tagName to remote $remote...');
|
||||||
|
git.run(
|
||||||
|
<String>['push', remote, tagName],
|
||||||
|
'publish the tag to the repo',
|
||||||
|
workingDirectory: checkoutDirectory.path,
|
||||||
|
);
|
||||||
|
stdio.printStatus('Tag push successful.');
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Repository cloneRepository(String? cloneName) {
|
Repository cloneRepository(String? cloneName) {
|
||||||
assert(localUpstream);
|
assert(localUpstream);
|
||||||
@ -480,9 +510,8 @@ class FrameworkRepository extends Repository {
|
|||||||
return FrameworkRepository(
|
return FrameworkRepository(
|
||||||
checkouts,
|
checkouts,
|
||||||
name: cloneName,
|
name: cloneName,
|
||||||
fetchRemote: Remote(
|
upstreamRemote: Remote(
|
||||||
name: RemoteName.upstream, url: 'file://${checkoutDirectory.path}/'),
|
name: RemoteName.upstream, url: 'file://${checkoutDirectory.path}/'),
|
||||||
useExistingCheckout: useExistingCheckout,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -547,17 +576,15 @@ class HostFrameworkRepository extends FrameworkRepository {
|
|||||||
HostFrameworkRepository({
|
HostFrameworkRepository({
|
||||||
required Checkouts checkouts,
|
required Checkouts checkouts,
|
||||||
String name = 'host-framework',
|
String name = 'host-framework',
|
||||||
bool useExistingCheckout = false,
|
|
||||||
required String upstreamPath,
|
required String upstreamPath,
|
||||||
}) : super(
|
}) : super(
|
||||||
checkouts,
|
checkouts,
|
||||||
name: name,
|
name: name,
|
||||||
fetchRemote: Remote(
|
upstreamRemote: Remote(
|
||||||
name: RemoteName.upstream,
|
name: RemoteName.upstream,
|
||||||
url: 'file://$upstreamPath/',
|
url: 'file://$upstreamPath/',
|
||||||
),
|
),
|
||||||
localUpstream: false,
|
localUpstream: false,
|
||||||
useExistingCheckout: useExistingCheckout,
|
|
||||||
) {
|
) {
|
||||||
_checkoutDirectory = checkouts.fileSystem.directory(upstreamPath);
|
_checkoutDirectory = checkouts.fileSystem.directory(upstreamPath);
|
||||||
}
|
}
|
||||||
@ -613,15 +640,15 @@ class EngineRepository extends Repository {
|
|||||||
this.checkouts, {
|
this.checkouts, {
|
||||||
String name = 'engine',
|
String name = 'engine',
|
||||||
String initialRef = EngineRepository.defaultBranch,
|
String initialRef = EngineRepository.defaultBranch,
|
||||||
Remote fetchRemote = const Remote(
|
Remote upstreamRemote = const Remote(
|
||||||
name: RemoteName.upstream, url: EngineRepository.defaultUpstream),
|
name: RemoteName.upstream, url: EngineRepository.defaultUpstream),
|
||||||
bool localUpstream = false,
|
bool localUpstream = false,
|
||||||
bool useExistingCheckout = false,
|
String? previousCheckoutLocation,
|
||||||
Remote? pushRemote,
|
Remote? mirrorRemote,
|
||||||
}) : super(
|
}) : super(
|
||||||
name: name,
|
name: name,
|
||||||
fetchRemote: fetchRemote,
|
upstreamRemote: upstreamRemote,
|
||||||
pushRemote: pushRemote,
|
mirrorRemote: mirrorRemote,
|
||||||
initialRef: initialRef,
|
initialRef: initialRef,
|
||||||
fileSystem: checkouts.fileSystem,
|
fileSystem: checkouts.fileSystem,
|
||||||
localUpstream: localUpstream,
|
localUpstream: localUpstream,
|
||||||
@ -629,7 +656,7 @@ class EngineRepository extends Repository {
|
|||||||
platform: checkouts.platform,
|
platform: checkouts.platform,
|
||||||
processManager: checkouts.processManager,
|
processManager: checkouts.processManager,
|
||||||
stdio: checkouts.stdio,
|
stdio: checkouts.stdio,
|
||||||
useExistingCheckout: useExistingCheckout,
|
previousCheckoutLocation: previousCheckoutLocation,
|
||||||
);
|
);
|
||||||
|
|
||||||
final Checkouts checkouts;
|
final Checkouts checkouts;
|
||||||
@ -669,9 +696,8 @@ class EngineRepository extends Repository {
|
|||||||
return EngineRepository(
|
return EngineRepository(
|
||||||
checkouts,
|
checkouts,
|
||||||
name: cloneName,
|
name: cloneName,
|
||||||
fetchRemote: Remote(
|
upstreamRemote: Remote(
|
||||||
name: RemoteName.upstream, url: 'file://${checkoutDirectory.path}/'),
|
name: RemoteName.upstream, url: 'file://${checkoutDirectory.path}/'),
|
||||||
useExistingCheckout: useExistingCheckout,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,7 @@ import './stdio.dart';
|
|||||||
import './version.dart';
|
import './version.dart';
|
||||||
|
|
||||||
const String kIncrement = 'increment';
|
const String kIncrement = 'increment';
|
||||||
const String kCommit = 'commit';
|
const String kCandidateBranch = 'candidate-branch';
|
||||||
const String kRemoteName = 'remote';
|
const String kRemoteName = 'remote';
|
||||||
const String kJustPrint = 'just-print';
|
const String kJustPrint = 'just-print';
|
||||||
const String kYes = 'yes';
|
const String kYes = 'yes';
|
||||||
@ -40,9 +40,9 @@ class RollDevCommand extends Command<void> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
argParser.addOption(
|
argParser.addOption(
|
||||||
kCommit,
|
kCandidateBranch,
|
||||||
help: 'Specifies which git commit to roll to the dev branch. Required.',
|
help: 'Specifies which git branch to roll to the dev branch. Required.',
|
||||||
valueHelp: 'hash',
|
valueHelp: 'branch',
|
||||||
defaultsTo: null, // This option is required
|
defaultsTo: null, // This option is required
|
||||||
);
|
);
|
||||||
argParser.addFlag(
|
argParser.addFlag(
|
||||||
@ -112,17 +112,16 @@ bool rollDev({
|
|||||||
}) {
|
}) {
|
||||||
final String remoteName = argResults[kRemoteName] as String;
|
final String remoteName = argResults[kRemoteName] as String;
|
||||||
final String level = argResults[kIncrement] as String;
|
final String level = argResults[kIncrement] as String;
|
||||||
final String commit = argResults[kCommit] as String;
|
final String candidateBranch = argResults[kCandidateBranch] as String;
|
||||||
final bool justPrint = argResults[kJustPrint] as bool;
|
final bool justPrint = argResults[kJustPrint] as bool;
|
||||||
final bool autoApprove = argResults[kYes] as bool;
|
final bool autoApprove = argResults[kYes] as bool;
|
||||||
final bool force = argResults[kForce] as bool;
|
final bool force = argResults[kForce] as bool;
|
||||||
final bool skipTagging = argResults[kSkipTagging] as bool;
|
final bool skipTagging = argResults[kSkipTagging] as bool;
|
||||||
|
|
||||||
if (level == null || commit == null) {
|
if (level == null || candidateBranch == null) {
|
||||||
stdio.printStatus(
|
throw Exception(
|
||||||
'roll_dev.dart --increment=level --commit=hash • update the version tags '
|
'roll_dev.dart --$kIncrement=level --$kCandidateBranch=branch • update the version tags '
|
||||||
'and roll a new dev build.\n$usage');
|
'and roll a new dev build.\n$usage');
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
final String remoteUrl = repository.remoteUrl(remoteName);
|
final String remoteUrl = repository.remoteUrl(remoteName);
|
||||||
@ -136,14 +135,16 @@ bool rollDev({
|
|||||||
repository.fetch(remoteName);
|
repository.fetch(remoteName);
|
||||||
|
|
||||||
// Verify [commit] is valid
|
// Verify [commit] is valid
|
||||||
repository.reverseParse(commit);
|
final String commit = repository.reverseParse(candidateBranch);
|
||||||
|
|
||||||
stdio.printStatus('remoteName is $remoteName');
|
stdio.printStatus('remoteName is $remoteName');
|
||||||
final Version lastVersion =
|
// Get the name of the last dev release
|
||||||
Version.fromString(repository.getFullTag(remoteName));
|
final Version lastVersion = Version.fromString(
|
||||||
|
repository.getFullTag(remoteName, 'dev'),
|
||||||
|
);
|
||||||
|
|
||||||
final Version version =
|
final Version version =
|
||||||
skipTagging ? lastVersion : Version.increment(lastVersion, level);
|
skipTagging ? lastVersion : Version.fromCandidateBranch(candidateBranch);
|
||||||
final String tagName = version.toString();
|
final String tagName = version.toString();
|
||||||
|
|
||||||
if (repository.reverseParse(lastVersion.toString()).contains(commit.trim())) {
|
if (repository.reverseParse(lastVersion.toString()).contains(commit.trim())) {
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
// @dart = 2.8
|
// @dart = 2.8
|
||||||
|
|
||||||
import 'dart:convert' show jsonEncode;
|
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:fixnum/fixnum.dart';
|
import 'package:fixnum/fixnum.dart';
|
||||||
@ -20,6 +18,7 @@ import './proto/conductor_state.pbenum.dart' show ReleasePhase;
|
|||||||
import './repository.dart';
|
import './repository.dart';
|
||||||
import './state.dart';
|
import './state.dart';
|
||||||
import './stdio.dart';
|
import './stdio.dart';
|
||||||
|
import './version.dart';
|
||||||
|
|
||||||
const String kCandidateOption = 'candidate-branch';
|
const String kCandidateOption = 'candidate-branch';
|
||||||
const String kDartRevisionOption = 'dart-revision';
|
const String kDartRevisionOption = 'dart-revision';
|
||||||
@ -28,6 +27,7 @@ const String kEngineUpstreamOption = 'engine-upstream';
|
|||||||
const String kFrameworkCherrypicksOption = 'framework-cherrypicks';
|
const String kFrameworkCherrypicksOption = 'framework-cherrypicks';
|
||||||
const String kFrameworkMirrorOption = 'framework-mirror';
|
const String kFrameworkMirrorOption = 'framework-mirror';
|
||||||
const String kFrameworkUpstreamOption = 'framework-upstream';
|
const String kFrameworkUpstreamOption = 'framework-upstream';
|
||||||
|
const String kIncrementOption = 'increment';
|
||||||
const String kEngineMirrorOption = 'engine-mirror';
|
const String kEngineMirrorOption = 'engine-mirror';
|
||||||
const String kReleaseOption = 'release-channel';
|
const String kReleaseOption = 'release-channel';
|
||||||
const String kStateOption = 'state-file';
|
const String kStateOption = 'state-file';
|
||||||
@ -91,6 +91,18 @@ class StartCommand extends Command<void> {
|
|||||||
kDartRevisionOption,
|
kDartRevisionOption,
|
||||||
help: 'New Dart revision to cherrypick.',
|
help: 'New Dart revision to cherrypick.',
|
||||||
);
|
);
|
||||||
|
argParser.addOption(
|
||||||
|
kIncrementOption,
|
||||||
|
help: 'Specifies which part of the x.y.z version number to increment. Required.',
|
||||||
|
valueHelp: 'level',
|
||||||
|
allowed: <String>['y', 'z', 'm', 'n'],
|
||||||
|
allowedHelp: <String, String>{
|
||||||
|
'y': 'Indicates the first dev release after a beta release.',
|
||||||
|
'z': 'Indicates a hotfix to a stable release.',
|
||||||
|
'm': 'Indicates a standard dev release.',
|
||||||
|
'n': 'Indicates a hotfix to a dev release.',
|
||||||
|
},
|
||||||
|
);
|
||||||
final Git git = Git(processManager);
|
final Git git = Git(processManager);
|
||||||
conductorVersion = git.getOutput(
|
conductorVersion = git.getOutput(
|
||||||
<String>['rev-parse', 'HEAD'],
|
<String>['rev-parse', 'HEAD'],
|
||||||
@ -183,6 +195,12 @@ class StartCommand extends Command<void> {
|
|||||||
platform.environment,
|
platform.environment,
|
||||||
allowNull: true,
|
allowNull: true,
|
||||||
);
|
);
|
||||||
|
final String incrementLetter = getValueFromEnvOrArgs(
|
||||||
|
kIncrementOption,
|
||||||
|
argResults,
|
||||||
|
platform.environment,
|
||||||
|
);
|
||||||
|
|
||||||
if (!releaseCandidateBranchRegex.hasMatch(candidateBranch)) {
|
if (!releaseCandidateBranchRegex.hasMatch(candidateBranch)) {
|
||||||
throw ConductorException(
|
throw ConductorException(
|
||||||
'Invalid release candidate branch "$candidateBranch". Text should '
|
'Invalid release candidate branch "$candidateBranch". Text should '
|
||||||
@ -200,11 +218,11 @@ class StartCommand extends Command<void> {
|
|||||||
final EngineRepository engine = EngineRepository(
|
final EngineRepository engine = EngineRepository(
|
||||||
checkouts,
|
checkouts,
|
||||||
initialRef: candidateBranch,
|
initialRef: candidateBranch,
|
||||||
fetchRemote: Remote(
|
upstreamRemote: Remote(
|
||||||
name: RemoteName.upstream,
|
name: RemoteName.upstream,
|
||||||
url: engineUpstream,
|
url: engineUpstream,
|
||||||
),
|
),
|
||||||
pushRemote: Remote(
|
mirrorRemote: Remote(
|
||||||
name: RemoteName.mirror,
|
name: RemoteName.mirror,
|
||||||
url: engineMirror,
|
url: engineMirror,
|
||||||
),
|
),
|
||||||
@ -249,15 +267,17 @@ class StartCommand extends Command<void> {
|
|||||||
checkoutPath: engine.checkoutDirectory.path,
|
checkoutPath: engine.checkoutDirectory.path,
|
||||||
cherrypicks: engineCherrypicks,
|
cherrypicks: engineCherrypicks,
|
||||||
dartRevision: dartRevision,
|
dartRevision: dartRevision,
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: engine.upstreamRemote.url),
|
||||||
|
mirror: pb.Remote(name: 'mirror', url: engine.mirrorRemote.url),
|
||||||
);
|
);
|
||||||
final FrameworkRepository framework = FrameworkRepository(
|
final FrameworkRepository framework = FrameworkRepository(
|
||||||
checkouts,
|
checkouts,
|
||||||
initialRef: candidateBranch,
|
initialRef: candidateBranch,
|
||||||
fetchRemote: Remote(
|
upstreamRemote: Remote(
|
||||||
name: RemoteName.upstream,
|
name: RemoteName.upstream,
|
||||||
url: frameworkUpstream,
|
url: frameworkUpstream,
|
||||||
),
|
),
|
||||||
pushRemote: Remote(
|
mirrorRemote: Remote(
|
||||||
name: RemoteName.mirror,
|
name: RemoteName.mirror,
|
||||||
url: frameworkMirror,
|
url: frameworkMirror,
|
||||||
),
|
),
|
||||||
@ -287,6 +307,16 @@ class StartCommand extends Command<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Get framework version
|
||||||
|
final Version lastVersion = Version.fromString(framework.getFullTag(framework.upstreamRemote.name, candidateBranch, exact: false));
|
||||||
|
Version nextVersion;
|
||||||
|
if (incrementLetter == 'm') {
|
||||||
|
nextVersion = Version.fromCandidateBranch(candidateBranch);
|
||||||
|
} else {
|
||||||
|
nextVersion = Version.increment(lastVersion, incrementLetter);
|
||||||
|
}
|
||||||
|
state.releaseVersion = nextVersion.toString();
|
||||||
|
|
||||||
final String frameworkHead = framework.reverseParse('HEAD');
|
final String frameworkHead = framework.reverseParse('HEAD');
|
||||||
state.framework = pb.Repository(
|
state.framework = pb.Repository(
|
||||||
candidateBranch: candidateBranch,
|
candidateBranch: candidateBranch,
|
||||||
@ -294,20 +324,17 @@ class StartCommand extends Command<void> {
|
|||||||
currentGitHead: frameworkHead,
|
currentGitHead: frameworkHead,
|
||||||
checkoutPath: framework.checkoutDirectory.path,
|
checkoutPath: framework.checkoutDirectory.path,
|
||||||
cherrypicks: frameworkCherrypicks,
|
cherrypicks: frameworkCherrypicks,
|
||||||
|
upstream: pb.Remote(name: 'upstream', url: framework.upstreamRemote.url),
|
||||||
|
mirror: pb.Remote(name: 'mirror', url: framework.mirrorRemote.url),
|
||||||
);
|
);
|
||||||
|
|
||||||
state.lastPhase = ReleasePhase.INITIALIZE;
|
state.currentPhase = ReleasePhase.APPLY_ENGINE_CHERRYPICKS;
|
||||||
|
|
||||||
state.conductorVersion = conductorVersion;
|
state.conductorVersion = conductorVersion;
|
||||||
|
|
||||||
stdio.printTrace('Writing state to file ${stateFile.path}...');
|
stdio.printTrace('Writing state to file ${stateFile.path}...');
|
||||||
|
|
||||||
state.logs.addAll(stdio.logs);
|
writeStateToFile(stateFile, state, stdio.logs);
|
||||||
|
|
||||||
stateFile.writeAsStringSync(
|
|
||||||
jsonEncode(state.toProto3Json()),
|
|
||||||
flush: true,
|
|
||||||
);
|
|
||||||
|
|
||||||
stdio.printStatus(presentState(state));
|
stdio.printStatus(presentState(state));
|
||||||
}
|
}
|
||||||
@ -340,8 +367,8 @@ class StartCommand extends Command<void> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final String branchPoint = repository.branchPoint(
|
final String branchPoint = repository.branchPoint(
|
||||||
'${repository.fetchRemote.name}/$upstreamRef',
|
'${repository.upstreamRemote.name}/$upstreamRef',
|
||||||
'${repository.fetchRemote.name}/$releaseRef',
|
'${repository.upstreamRemote.name}/$releaseRef',
|
||||||
);
|
);
|
||||||
|
|
||||||
// `git rev-list` returns newest first, so reverse this list
|
// `git rev-list` returns newest first, so reverse this list
|
||||||
|
@ -4,6 +4,9 @@
|
|||||||
|
|
||||||
// @dart = 2.8
|
// @dart = 2.8
|
||||||
|
|
||||||
|
import 'dart:convert' show jsonDecode, jsonEncode;
|
||||||
|
|
||||||
|
import 'package:file/file.dart' show File;
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
import './globals.dart';
|
import './globals.dart';
|
||||||
@ -37,6 +40,7 @@ String presentState(pb.ConductorState state) {
|
|||||||
final StringBuffer buffer = StringBuffer();
|
final StringBuffer buffer = StringBuffer();
|
||||||
buffer.writeln('Conductor version: ${state.conductorVersion}');
|
buffer.writeln('Conductor version: ${state.conductorVersion}');
|
||||||
buffer.writeln('Release channel: ${state.releaseChannel}');
|
buffer.writeln('Release channel: ${state.releaseChannel}');
|
||||||
|
buffer.writeln('Release version: ${state.releaseVersion}');
|
||||||
buffer.writeln('');
|
buffer.writeln('');
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
'Release started at: ${DateTime.fromMillisecondsSinceEpoch(state.createdDate.toInt())}');
|
'Release started at: ${DateTime.fromMillisecondsSinceEpoch(state.createdDate.toInt())}');
|
||||||
@ -76,14 +80,14 @@ String presentState(pb.ConductorState state) {
|
|||||||
buffer.writeln('0 Framework cherrypicks.');
|
buffer.writeln('0 Framework cherrypicks.');
|
||||||
}
|
}
|
||||||
buffer.writeln('');
|
buffer.writeln('');
|
||||||
if (state.lastPhase == ReleasePhase.VERIFY_RELEASE) {
|
if (state.currentPhase == ReleasePhase.VERIFY_RELEASE) {
|
||||||
buffer.writeln(
|
buffer.writeln(
|
||||||
'${state.releaseChannel} release ${state.releaseVersion} has been published and verified.\n',
|
'${state.releaseChannel} release ${state.releaseVersion} has been published and verified.\n',
|
||||||
);
|
);
|
||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
buffer.writeln('The next step is:');
|
buffer.writeln('The current phase is:');
|
||||||
buffer.writeln(presentPhases(state.lastPhase));
|
buffer.writeln(presentPhases(state.currentPhase));
|
||||||
|
|
||||||
buffer.writeln(phaseInstructions(state));
|
buffer.writeln(phaseInstructions(state));
|
||||||
buffer.writeln('');
|
buffer.writeln('');
|
||||||
@ -91,15 +95,14 @@ String presentState(pb.ConductorState state) {
|
|||||||
return buffer.toString();
|
return buffer.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
String presentPhases(ReleasePhase lastPhase) {
|
String presentPhases(ReleasePhase currentPhase) {
|
||||||
final ReleasePhase nextPhase = getNextPhase(lastPhase);
|
|
||||||
final StringBuffer buffer = StringBuffer();
|
final StringBuffer buffer = StringBuffer();
|
||||||
bool phaseCompleted = true;
|
bool phaseCompleted = true;
|
||||||
|
|
||||||
for (final ReleasePhase phase in ReleasePhase.values) {
|
for (final ReleasePhase phase in ReleasePhase.values) {
|
||||||
if (phase == nextPhase) {
|
if (phase == currentPhase) {
|
||||||
// This phase will execute the next time `conductor next` is run.
|
// This phase will execute the next time `conductor next` is run.
|
||||||
buffer.writeln('> ${phase.name} (next)');
|
buffer.writeln('> ${phase.name} (current)');
|
||||||
phaseCompleted = false;
|
phaseCompleted = false;
|
||||||
} else if (phaseCompleted) {
|
} else if (phaseCompleted) {
|
||||||
// This phase was already completed.
|
// This phase was already completed.
|
||||||
@ -113,8 +116,8 @@ String presentPhases(ReleasePhase lastPhase) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String phaseInstructions(pb.ConductorState state) {
|
String phaseInstructions(pb.ConductorState state) {
|
||||||
switch (state.lastPhase) {
|
switch (state.currentPhase) {
|
||||||
case ReleasePhase.INITIALIZE:
|
case ReleasePhase.APPLY_ENGINE_CHERRYPICKS:
|
||||||
if (state.engine.cherrypicks.isEmpty) {
|
if (state.engine.cherrypicks.isEmpty) {
|
||||||
return <String>[
|
return <String>[
|
||||||
'There are no engine cherrypicks, so issue `conductor next` to continue',
|
'There are no engine cherrypicks, so issue `conductor next` to continue',
|
||||||
@ -128,31 +131,33 @@ String phaseInstructions(pb.ConductorState state) {
|
|||||||
'\t${cherrypick.trunkRevision}',
|
'\t${cherrypick.trunkRevision}',
|
||||||
'See $kReleaseDocumentationUrl for more information.',
|
'See $kReleaseDocumentationUrl for more information.',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
case ReleasePhase.APPLY_ENGINE_CHERRYPICKS:
|
case ReleasePhase.CODESIGN_ENGINE_BINARIES:
|
||||||
return <String>[
|
return <String>[
|
||||||
'You must verify Engine CI builds are successful and then codesign the',
|
'You must verify Engine CI builds are successful and then codesign the',
|
||||||
'binaries at revision ${state.engine.currentGitHead}.',
|
'binaries at revision ${state.engine.currentGitHead}.',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
case ReleasePhase.CODESIGN_ENGINE_BINARIES:
|
case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS:
|
||||||
|
final List<pb.Cherrypick> outstandingCherrypicks = state.framework.cherrypicks.where(
|
||||||
|
(pb.Cherrypick cp) {
|
||||||
|
return cp.state == pb.CherrypickState.PENDING || cp.state == pb.CherrypickState.PENDING_WITH_CONFLICT;
|
||||||
|
},
|
||||||
|
).toList();
|
||||||
return <String>[
|
return <String>[
|
||||||
'You must now manually apply the following framework cherrypicks to the checkout',
|
'You must now manually apply the following framework cherrypicks to the checkout',
|
||||||
'at ${state.framework.checkoutPath} in order:',
|
'at ${state.framework.checkoutPath} in order:',
|
||||||
for (final pb.Cherrypick cherrypick in state.framework.cherrypicks)
|
for (final pb.Cherrypick cherrypick in outstandingCherrypicks)
|
||||||
'\t${cherrypick.trunkRevision}',
|
'\t${cherrypick.trunkRevision}',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS:
|
case ReleasePhase.PUBLISH_VERSION:
|
||||||
return <String>[
|
return <String>[
|
||||||
'You must verify Framework CI builds are successful.',
|
'You must verify Framework CI builds are successful.',
|
||||||
'See $kReleaseDocumentationUrl for more information.',
|
'See $kReleaseDocumentationUrl for more information.',
|
||||||
].join('\n');
|
].join('\n');
|
||||||
case ReleasePhase.PUBLISH_VERSION:
|
|
||||||
return 'Issue `conductor next` to publish your release to the release branch.';
|
|
||||||
case ReleasePhase.PUBLISH_CHANNEL:
|
case ReleasePhase.PUBLISH_CHANNEL:
|
||||||
return <String>[
|
return 'Issue `conductor next` to publish your release to the release branch.';
|
||||||
'Release archive packages must be verified on cloud storage. Issue',
|
|
||||||
'`conductor next` to check if they are ready.',
|
|
||||||
].join('\n');
|
|
||||||
case ReleasePhase.VERIFY_RELEASE:
|
case ReleasePhase.VERIFY_RELEASE:
|
||||||
|
return 'Release archive packages must be verified on cloud storage.';
|
||||||
|
case ReleasePhase.RELEASE_COMPLETED:
|
||||||
return 'This release has been completed.';
|
return 'This release has been completed.';
|
||||||
}
|
}
|
||||||
assert(false);
|
assert(false);
|
||||||
@ -161,12 +166,29 @@ String phaseInstructions(pb.ConductorState state) {
|
|||||||
|
|
||||||
/// Returns the next phase in the ReleasePhase enum.
|
/// Returns the next phase in the ReleasePhase enum.
|
||||||
///
|
///
|
||||||
/// Will throw a [ConductorException] if [ReleasePhase.RELEASE_VERIFIED] is
|
/// Will throw a [ConductorException] if [ReleasePhase.RELEASE_COMPLETED] is
|
||||||
/// passed as an argument, as there is no next phase.
|
/// passed as an argument, as there is no next phase.
|
||||||
ReleasePhase getNextPhase(ReleasePhase previousPhase) {
|
ReleasePhase getNextPhase(ReleasePhase previousPhase) {
|
||||||
assert(previousPhase != null);
|
assert(previousPhase != null);
|
||||||
if (previousPhase == ReleasePhase.VERIFY_RELEASE) {
|
if (previousPhase == ReleasePhase.RELEASE_COMPLETED) {
|
||||||
throw ConductorException('There is no next ReleasePhase!');
|
throw ConductorException('There is no next ReleasePhase!');
|
||||||
}
|
}
|
||||||
return ReleasePhase.valueOf(previousPhase.value + 1);
|
return ReleasePhase.valueOf(previousPhase.value + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void writeStateToFile(File file, pb.ConductorState state, List<String> logs) {
|
||||||
|
state.logs.addAll(logs);
|
||||||
|
file.writeAsStringSync(
|
||||||
|
jsonEncode(state.toProto3Json()),
|
||||||
|
flush: true,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
pb.ConductorState readStateFromFile(File file) {
|
||||||
|
final pb.ConductorState state = pb.ConductorState();
|
||||||
|
final String stateAsString = file.readAsStringSync();
|
||||||
|
state.mergeFromProto3Json(
|
||||||
|
jsonDecode(stateAsString),
|
||||||
|
);
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
@ -4,8 +4,6 @@
|
|||||||
|
|
||||||
// @dart = 2.8
|
// @dart = 2.8
|
||||||
|
|
||||||
import 'dart:convert' show jsonDecode;
|
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
@ -59,8 +57,8 @@ class StatusCommand extends Command<void> {
|
|||||||
'No persistent state file found at ${argResults[kStateOption]}.');
|
'No persistent state file found at ${argResults[kStateOption]}.');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final pb.ConductorState state = pb.ConductorState();
|
final pb.ConductorState state = readStateFromFile(stateFile);
|
||||||
state.mergeFromProto3Json(jsonDecode(stateFile.readAsStringSync()));
|
|
||||||
stdio.printStatus(presentState(state));
|
stdio.printStatus(presentState(state));
|
||||||
if (argResults[kVerboseFlag] as bool) {
|
if (argResults[kVerboseFlag] as bool) {
|
||||||
stdio.printStatus('\nLogs:');
|
stdio.printStatus('\nLogs:');
|
||||||
|
@ -2,6 +2,8 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import './globals.dart' show ConductorException;
|
||||||
|
|
||||||
/// Possible string formats that `flutter --version` can return.
|
/// Possible string formats that `flutter --version` can return.
|
||||||
enum VersionType {
|
enum VersionType {
|
||||||
/// A stable flutter release.
|
/// A stable flutter release.
|
||||||
@ -20,12 +22,18 @@ enum VersionType {
|
|||||||
///
|
///
|
||||||
/// The last number is the number of commits past the last tagged version.
|
/// The last number is the number of commits past the last tagged version.
|
||||||
latest,
|
latest,
|
||||||
|
|
||||||
|
/// A master channel flutter version from git describe.
|
||||||
|
///
|
||||||
|
/// Example: '1.2.3-4.0.pre-10-gabc123'.
|
||||||
|
gitDescribe,
|
||||||
}
|
}
|
||||||
|
|
||||||
final Map<VersionType, RegExp> versionPatterns = <VersionType, RegExp>{
|
final Map<VersionType, RegExp> versionPatterns = <VersionType, RegExp>{
|
||||||
VersionType.stable: RegExp(r'^(\d+)\.(\d+)\.(\d+)$'),
|
VersionType.stable: RegExp(r'^(\d+)\.(\d+)\.(\d+)$'),
|
||||||
VersionType.development: RegExp(r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre$'),
|
VersionType.development: RegExp(r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre$'),
|
||||||
VersionType.latest: RegExp(r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre\.(\d+)$'),
|
VersionType.latest: RegExp(r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre\.(\d+)$'),
|
||||||
|
VersionType.gitDescribe: RegExp(r'^(\d+)\.(\d+)\.(\d+)-(\d+)\.(\d+)\.pre-(\d+)-g[a-f0-9]+$'),
|
||||||
};
|
};
|
||||||
|
|
||||||
class Version {
|
class Version {
|
||||||
@ -54,6 +62,10 @@ class Version {
|
|||||||
assert(n != null);
|
assert(n != null);
|
||||||
assert(commits != null);
|
assert(commits != null);
|
||||||
break;
|
break;
|
||||||
|
case VersionType.gitDescribe:
|
||||||
|
throw ConductorException(
|
||||||
|
'VersionType.gitDescribe not supported! Use VersionType.latest instead.',
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -115,6 +127,24 @@ class Version {
|
|||||||
type: VersionType.latest,
|
type: VersionType.latest,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
match = versionPatterns[VersionType.gitDescribe]!.firstMatch(versionString);
|
||||||
|
if (match != null) {
|
||||||
|
// parse latest
|
||||||
|
final List<int> parts = match.groups(
|
||||||
|
<int>[1, 2, 3, 4, 5, 6],
|
||||||
|
).map(
|
||||||
|
(String? s) => int.parse(s!),
|
||||||
|
).toList();
|
||||||
|
return Version(
|
||||||
|
x: parts[0],
|
||||||
|
y: parts[1],
|
||||||
|
z: parts[2],
|
||||||
|
m: parts[3],
|
||||||
|
n: parts[4],
|
||||||
|
commits: parts[5],
|
||||||
|
type: VersionType.latest,
|
||||||
|
);
|
||||||
|
}
|
||||||
throw Exception('${versionString.trim()} cannot be parsed');
|
throw Exception('${versionString.trim()} cannot be parsed');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -131,7 +161,7 @@ class Version {
|
|||||||
int? nextM = previousVersion.m;
|
int? nextM = previousVersion.m;
|
||||||
int? nextN = previousVersion.n;
|
int? nextN = previousVersion.n;
|
||||||
if (nextVersionType == null) {
|
if (nextVersionType == null) {
|
||||||
if (previousVersion.type == VersionType.latest) {
|
if (previousVersion.type == VersionType.latest || previousVersion.type == VersionType.gitDescribe) {
|
||||||
nextVersionType = VersionType.development;
|
nextVersionType = VersionType.development;
|
||||||
} else {
|
} else {
|
||||||
nextVersionType = previousVersion.type;
|
nextVersionType = previousVersion.type;
|
||||||
@ -157,10 +187,7 @@ class Version {
|
|||||||
nextZ += 1;
|
nextZ += 1;
|
||||||
break;
|
break;
|
||||||
case 'm':
|
case 'm':
|
||||||
// Regular dev release.
|
assert(false, "Do not increment 'm' via Version.increment, use instead Version.fromCandidateBranch()");
|
||||||
assert(previousVersion.type == VersionType.development);
|
|
||||||
nextM = nextM! + 1;
|
|
||||||
nextN = 0;
|
|
||||||
break;
|
break;
|
||||||
case 'n':
|
case 'n':
|
||||||
// Hotfix to internal roll.
|
// Hotfix to internal roll.
|
||||||
@ -179,6 +206,31 @@ class Version {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
factory Version.fromCandidateBranch(String branchName) {
|
||||||
|
// Regular dev release.
|
||||||
|
final RegExp pattern = RegExp(r'flutter-(\d+)\.(\d+)-candidate.(\d+)');
|
||||||
|
final RegExpMatch? match = pattern.firstMatch(branchName);
|
||||||
|
late final int x;
|
||||||
|
late final int y;
|
||||||
|
late final int m;
|
||||||
|
try {
|
||||||
|
x = int.parse(match!.group(1)!);
|
||||||
|
y = int.parse(match.group(2)!);
|
||||||
|
m = int.parse(match.group(3)!);
|
||||||
|
} on Exception {
|
||||||
|
throw ConductorException('branch named $branchName not recognized as a valid candidate branch');
|
||||||
|
}
|
||||||
|
|
||||||
|
return Version(
|
||||||
|
type: VersionType.development,
|
||||||
|
x: x,
|
||||||
|
y: y,
|
||||||
|
z: 0,
|
||||||
|
m: m,
|
||||||
|
n: 0,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/// Major version.
|
/// Major version.
|
||||||
final int x;
|
final int x;
|
||||||
|
|
||||||
@ -208,6 +260,8 @@ class Version {
|
|||||||
return '$x.$y.$z-$m.$n.pre';
|
return '$x.$y.$z-$m.$n.pre';
|
||||||
case VersionType.latest:
|
case VersionType.latest:
|
||||||
return '$x.$y.$z-$m.$n.pre.$commits';
|
return '$x.$y.$z-$m.$n.pre.$commits';
|
||||||
|
case VersionType.gitDescribe:
|
||||||
|
return '$x.$y.$z-$m.$n.pre.$commits';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
import 'package:conductor/codesign.dart';
|
import 'package:conductor/codesign.dart';
|
||||||
import 'package:conductor/globals.dart';
|
|
||||||
import 'package:conductor/repository.dart';
|
import 'package:conductor/repository.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:file/memory.dart';
|
import 'package:file/memory.dart';
|
||||||
@ -16,7 +15,7 @@ import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
|
|||||||
void main() {
|
void main() {
|
||||||
group('codesign command', () {
|
group('codesign command', () {
|
||||||
const String flutterRoot = '/flutter';
|
const String flutterRoot = '/flutter';
|
||||||
const String checkoutsParentDirectory = '$flutterRoot/dev/tools/';
|
const String checkoutsParentDirectory = '$flutterRoot/dev/conductor/';
|
||||||
const String flutterCache =
|
const String flutterCache =
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache';
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework/bin/cache';
|
||||||
const String flutterBin =
|
const String flutterBin =
|
||||||
@ -387,7 +386,6 @@ void main() {
|
|||||||
stdout: 'application/x-mach-binary',
|
stdout: 'application/x-mach-binary',
|
||||||
),
|
),
|
||||||
]);
|
]);
|
||||||
try {
|
|
||||||
await runner.run(<String>[
|
await runner.run(<String>[
|
||||||
'codesign',
|
'codesign',
|
||||||
'--$kVerify',
|
'--$kVerify',
|
||||||
@ -395,10 +393,6 @@ void main() {
|
|||||||
'--$kRevision',
|
'--$kRevision',
|
||||||
revision,
|
revision,
|
||||||
]);
|
]);
|
||||||
} on ConductorException {
|
|
||||||
//print(stdio.error);
|
|
||||||
rethrow;
|
|
||||||
}
|
|
||||||
expect(
|
expect(
|
||||||
processManager.hasRemainingExpectations,
|
processManager.hasRemainingExpectations,
|
||||||
false,
|
false,
|
||||||
|
@ -9,6 +9,16 @@ import 'package:test/test.dart';
|
|||||||
|
|
||||||
export 'package:test/test.dart' hide isInstanceOf;
|
export 'package:test/test.dart' hide isInstanceOf;
|
||||||
|
|
||||||
|
Matcher throwsAssertionWith(String messageSubString) {
|
||||||
|
return throwsA(
|
||||||
|
isA<AssertionError>().having(
|
||||||
|
(AssertionError e) => e.toString(),
|
||||||
|
'description',
|
||||||
|
contains(messageSubString),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
Matcher throwsExceptionWith(String messageSubString) {
|
Matcher throwsExceptionWith(String messageSubString) {
|
||||||
return throwsA(
|
return throwsA(
|
||||||
isA<Exception>().having(
|
isA<Exception>().having(
|
||||||
@ -28,11 +38,12 @@ class TestStdio extends Stdio {
|
|||||||
String get error => logs.where((String log) => log.startsWith(r'[error] ')).join('\n');
|
String get error => logs.where((String log) => log.startsWith(r'[error] ')).join('\n');
|
||||||
|
|
||||||
String get stdout => logs.where((String log) {
|
String get stdout => logs.where((String log) {
|
||||||
return log.startsWith(r'[status] ') || log.startsWith(r'[trace] ');
|
return log.startsWith(r'[status] ') || log.startsWith(r'[trace] ') || log.startsWith(r'[write] ');
|
||||||
}).join('\n');
|
}).join('\n');
|
||||||
|
|
||||||
final bool verbose;
|
final bool verbose;
|
||||||
late final List<String> _stdin;
|
late final List<String> _stdin;
|
||||||
|
List<String> get stdin => _stdin;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String readLineSync() {
|
String readLineSync() {
|
||||||
@ -46,7 +57,7 @@ class TestStdio extends Stdio {
|
|||||||
class FakeArgResults implements ArgResults {
|
class FakeArgResults implements ArgResults {
|
||||||
FakeArgResults({
|
FakeArgResults({
|
||||||
required String level,
|
required String level,
|
||||||
required String commit,
|
required String candidateBranch,
|
||||||
String remote = 'upstream',
|
String remote = 'upstream',
|
||||||
bool justPrint = false,
|
bool justPrint = false,
|
||||||
bool autoApprove = true, // so we don't have to mock stdin
|
bool autoApprove = true, // so we don't have to mock stdin
|
||||||
@ -55,7 +66,7 @@ class FakeArgResults implements ArgResults {
|
|||||||
bool skipTagging = false,
|
bool skipTagging = false,
|
||||||
}) : _parsedArgs = <String, dynamic>{
|
}) : _parsedArgs = <String, dynamic>{
|
||||||
'increment': level,
|
'increment': level,
|
||||||
'commit': commit,
|
'candidate-branch': candidateBranch,
|
||||||
'remote': remote,
|
'remote': remote,
|
||||||
'just-print': justPrint,
|
'just-print': justPrint,
|
||||||
'yes': autoApprove,
|
'yes': autoApprove,
|
||||||
|
572
dev/conductor/test/next_test.dart
Normal file
572
dev/conductor/test/next_test.dart
Normal file
@ -0,0 +1,572 @@
|
|||||||
|
// Copyright 2014 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.8
|
||||||
|
|
||||||
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:conductor/next.dart';
|
||||||
|
import 'package:conductor/proto/conductor_state.pb.dart' as pb;
|
||||||
|
import 'package:conductor/proto/conductor_state.pbenum.dart' show ReleasePhase;
|
||||||
|
import 'package:conductor/repository.dart';
|
||||||
|
import 'package:conductor/state.dart';
|
||||||
|
import 'package:file/memory.dart';
|
||||||
|
import 'package:meta/meta.dart';
|
||||||
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
|
import './common.dart';
|
||||||
|
import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
group('next command', () {
|
||||||
|
const String flutterRoot = '/flutter';
|
||||||
|
const String checkoutsParentDirectory = '$flutterRoot/dev/tools/';
|
||||||
|
const String candidateBranch = 'flutter-1.2-candidate.3';
|
||||||
|
final String localPathSeparator = const LocalPlatform().pathSeparator;
|
||||||
|
final String localOperatingSystem = const LocalPlatform().pathSeparator;
|
||||||
|
const String revision1 = 'abc123';
|
||||||
|
MemoryFileSystem fileSystem;
|
||||||
|
TestStdio stdio;
|
||||||
|
const String stateFile = '/state-file.json';
|
||||||
|
|
||||||
|
setUp(() {
|
||||||
|
stdio = TestStdio();
|
||||||
|
fileSystem = MemoryFileSystem.test();
|
||||||
|
});
|
||||||
|
|
||||||
|
CommandRunner<void> createRunner({
|
||||||
|
@required Checkouts checkouts,
|
||||||
|
}) {
|
||||||
|
final NextCommand command = NextCommand(
|
||||||
|
checkouts: checkouts,
|
||||||
|
);
|
||||||
|
return CommandRunner<void>('codesign-test', '')..addCommand(command);
|
||||||
|
}
|
||||||
|
|
||||||
|
test('throws if no state file found', () async {
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
expect(
|
||||||
|
() async => runner.run(<String>[
|
||||||
|
'next',
|
||||||
|
'--$kStateOption',
|
||||||
|
stateFile,
|
||||||
|
]),
|
||||||
|
throwsExceptionWith('No persistent state file found at $stateFile'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not prompt user and updates state.currentPhase from APPLY_ENGINE_CHERRYPICKS to CODESIGN_ENGINE_BINARIES if there are no engine cherrypicks', () async {
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
currentPhase: ReleasePhase.APPLY_ENGINE_CHERRYPICKS,
|
||||||
|
);
|
||||||
|
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.CODESIGN_ENGINE_BINARIES);
|
||||||
|
expect(stdio.error, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('updates state.lastPhase from APPLY_ENGINE_CHERRYPICKS to CODESIGN_ENGINE_BINARIES if user responds yes', () async {
|
||||||
|
const String remoteUrl = 'https://githost.com/org/repo.git';
|
||||||
|
stdio.stdin.add('y');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
engine: pb.Repository(
|
||||||
|
cherrypicks: <pb.Cherrypick>[
|
||||||
|
pb.Cherrypick(
|
||||||
|
trunkRevision: 'abc123',
|
||||||
|
state: pb.CherrypickState.PENDING,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
mirror: pb.Remote(url: remoteUrl),
|
||||||
|
),
|
||||||
|
currentPhase: ReleasePhase.APPLY_ENGINE_CHERRYPICKS,
|
||||||
|
);
|
||||||
|
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(stdio.stdout, contains(
|
||||||
|
'Are you ready to push your engine branch to the repository $remoteUrl? (y/n) '));
|
||||||
|
expect(finalState.currentPhase, ReleasePhase.CODESIGN_ENGINE_BINARIES);
|
||||||
|
expect(stdio.error, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not update state.currentPhase from CODESIGN_ENGINE_BINARIES if user responds no', () async {
|
||||||
|
stdio.stdin.add('n');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
engine: pb.Repository(
|
||||||
|
cherrypicks: <pb.Cherrypick>[
|
||||||
|
pb.Cherrypick(
|
||||||
|
trunkRevision: 'abc123',
|
||||||
|
state: pb.CherrypickState.PENDING,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
currentPhase: ReleasePhase.CODESIGN_ENGINE_BINARIES,
|
||||||
|
);
|
||||||
|
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(stdio.stdout, contains('Has CI passed for the engine PR and binaries been codesigned? (y/n) '));
|
||||||
|
expect(finalState.currentPhase, ReleasePhase.CODESIGN_ENGINE_BINARIES);
|
||||||
|
expect(stdio.error.contains('Aborting command.'), true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates state.currentPhase from CODESIGN_ENGINE_BINARIES to APPLY_FRAMEWORK_CHERRYPICKS if user responds yes', () async {
|
||||||
|
stdio.stdin.add('y');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
currentPhase: ReleasePhase.CODESIGN_ENGINE_BINARIES,
|
||||||
|
);
|
||||||
|
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(stdio.stdout, contains('Has CI passed for the engine PR and binaries been codesigned? (y/n) '));
|
||||||
|
expect(finalState.currentPhase, ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not prompt user and updates state.currentPhase from APPLY_FRAMEWORK_CHERRYPICKS to PUBLISH_VERSION if there are no framework cherrypicks', () async {
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
||||||
|
);
|
||||||
|
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(stdio.stdout, isNot(contains('Did you apply all framework cherrypicks? (y/n) ')));
|
||||||
|
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
||||||
|
expect(stdio.error, isEmpty);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('does not update state.currentPhase from APPLY_FRAMEWORK_CHERRYPICKS if user responds no', () async {
|
||||||
|
const String remoteUrl = 'https://githost.com/org/repo.git';
|
||||||
|
stdio.stdin.add('n');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
framework: pb.Repository(
|
||||||
|
cherrypicks: <pb.Cherrypick>[
|
||||||
|
pb.Cherrypick(
|
||||||
|
trunkRevision: 'abc123',
|
||||||
|
state: pb.CherrypickState.PENDING,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
mirror: pb.Remote(url: remoteUrl),
|
||||||
|
),
|
||||||
|
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
||||||
|
);
|
||||||
|
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(stdio.stdout, contains('Are you ready to push your framework branch to the repository $remoteUrl? (y/n) '));
|
||||||
|
expect(stdio.error, contains('Aborting command.'));
|
||||||
|
expect(finalState.currentPhase, ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates state.currentPhase from APPLY_FRAMEWORK_CHERRYPICKS to PUBLISH_VERSION if user responds yes', () async {
|
||||||
|
const String remoteUrl = 'https://githost.com/org/repo.git';
|
||||||
|
stdio.stdin.add('y');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
framework: pb.Repository(
|
||||||
|
cherrypicks: <pb.Cherrypick>[
|
||||||
|
pb.Cherrypick(
|
||||||
|
trunkRevision: 'abc123',
|
||||||
|
state: pb.CherrypickState.PENDING,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
mirror: pb.Remote(url: remoteUrl),
|
||||||
|
),
|
||||||
|
currentPhase: ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS,
|
||||||
|
);
|
||||||
|
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(stdio.stdout, contains('Are you ready to push your framework branch to the repository $remoteUrl? (y/n)'));
|
||||||
|
});
|
||||||
|
|
||||||
|
|
||||||
|
test('does not update state.currentPhase from PUBLISH_VERSION if user responds no', () async {
|
||||||
|
const String remoteName = 'upstream';
|
||||||
|
stdio.stdin.add('n');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'checkout', '$remoteName/$candidateBranch'],
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision1,
|
||||||
|
),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
currentPhase: ReleasePhase.PUBLISH_VERSION,
|
||||||
|
framework: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
upstream: pb.Remote(url: FrameworkRepository.defaultUpstream),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
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(stdio.stdout, contains('Has CI passed for the framework PR?'));
|
||||||
|
expect(stdio.error, contains('Aborting command.'));
|
||||||
|
expect(finalState.currentPhase, ReleasePhase.PUBLISH_VERSION);
|
||||||
|
expect(finalState.logs, stdio.logs);
|
||||||
|
expect(processManager.hasRemainingExpectations, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('updates state.currentPhase from PUBLISH_VERSION to PUBLISH_CHANNEL if user responds yes', () async {
|
||||||
|
const String remoteName = 'upstream';
|
||||||
|
const String releaseVersion = '1.2.0-3.0.pre';
|
||||||
|
stdio.stdin.add('y');
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(<FakeCommand>[
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'checkout', '$remoteName/$candidateBranch'],
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
|
stdout: revision1,
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'tag', releaseVersion, revision1],
|
||||||
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>['git', 'push', remoteName, releaseVersion],
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
currentPhase: ReleasePhase.PUBLISH_VERSION,
|
||||||
|
framework: pb.Repository(
|
||||||
|
candidateBranch: candidateBranch,
|
||||||
|
upstream: pb.Remote(url: FrameworkRepository.defaultUpstream),
|
||||||
|
),
|
||||||
|
releaseVersion: releaseVersion,
|
||||||
|
);
|
||||||
|
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_CHANNEL);
|
||||||
|
expect(stdio.stdout, contains('Has CI passed for the framework PR?'));
|
||||||
|
expect(finalState.logs, stdio.logs);
|
||||||
|
expect(processManager.hasRemainingExpectations, false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('throws exception if state.currentPhase is RELEASE_COMPLETED', () async {
|
||||||
|
final FakeProcessManager processManager = FakeProcessManager.list(
|
||||||
|
<FakeCommand>[],
|
||||||
|
);
|
||||||
|
final FakePlatform platform = FakePlatform(
|
||||||
|
environment: <String, String>{
|
||||||
|
'HOME': <String>['path', 'to', 'home'].join(localPathSeparator),
|
||||||
|
},
|
||||||
|
operatingSystem: localOperatingSystem,
|
||||||
|
pathSeparator: localPathSeparator,
|
||||||
|
);
|
||||||
|
final pb.ConductorState state = pb.ConductorState(
|
||||||
|
currentPhase: ReleasePhase.RELEASE_COMPLETED,
|
||||||
|
);
|
||||||
|
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);
|
||||||
|
expect(
|
||||||
|
() async => runner.run(<String>[
|
||||||
|
'next',
|
||||||
|
'--$kStateOption',
|
||||||
|
stateFile,
|
||||||
|
]),
|
||||||
|
throwsExceptionWith('This release is finished.'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}, onPlatform: <String, dynamic>{
|
||||||
|
'windows': const Skip('Flutter Conductor only supported on macos/linux'),
|
||||||
|
});
|
||||||
|
}
|
@ -1,91 +0,0 @@
|
|||||||
// Copyright 2014 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.
|
|
||||||
|
|
||||||
import 'package:conductor/repository.dart';
|
|
||||||
import 'package:conductor/roll_dev.dart' show rollDev;
|
|
||||||
import 'package:conductor/version.dart';
|
|
||||||
import 'package:file/file.dart';
|
|
||||||
import 'package:file/local.dart';
|
|
||||||
import 'package:platform/platform.dart';
|
|
||||||
import 'package:process/process.dart';
|
|
||||||
|
|
||||||
import './common.dart';
|
|
||||||
|
|
||||||
void main() {
|
|
||||||
group('roll-dev', () {
|
|
||||||
late TestStdio stdio;
|
|
||||||
late Platform platform;
|
|
||||||
late ProcessManager processManager;
|
|
||||||
late FileSystem fileSystem;
|
|
||||||
const String usageString = 'Usage: flutter conductor.';
|
|
||||||
|
|
||||||
late Checkouts checkouts;
|
|
||||||
late FrameworkRepository frameworkUpstream;
|
|
||||||
late FrameworkRepository framework;
|
|
||||||
late Directory tempDir;
|
|
||||||
|
|
||||||
setUp(() {
|
|
||||||
platform = const LocalPlatform();
|
|
||||||
fileSystem = const LocalFileSystem();
|
|
||||||
processManager = const LocalProcessManager();
|
|
||||||
stdio = TestStdio(verbose: true);
|
|
||||||
tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_conductor_checkouts.');
|
|
||||||
checkouts = Checkouts(
|
|
||||||
fileSystem: fileSystem,
|
|
||||||
parentDirectory: tempDir,
|
|
||||||
platform: platform,
|
|
||||||
processManager: processManager,
|
|
||||||
stdio: stdio,
|
|
||||||
);
|
|
||||||
|
|
||||||
frameworkUpstream = FrameworkRepository(checkouts, localUpstream: true);
|
|
||||||
|
|
||||||
// This repository has [frameworkUpstream] set as its push/pull remote.
|
|
||||||
framework = FrameworkRepository(
|
|
||||||
checkouts,
|
|
||||||
name: 'test-framework',
|
|
||||||
fetchRemote: Remote(name: RemoteName.upstream, url: 'file://${frameworkUpstream.checkoutDirectory.path}/'),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('increment m', () {
|
|
||||||
final Version initialVersion = framework.flutterVersion();
|
|
||||||
|
|
||||||
final String latestCommit = framework.authorEmptyCommit();
|
|
||||||
|
|
||||||
final FakeArgResults fakeArgResults = FakeArgResults(
|
|
||||||
level: 'm',
|
|
||||||
commit: latestCommit,
|
|
||||||
// Ensure this test passes after a dev release with hotfixes
|
|
||||||
force: true,
|
|
||||||
remote: 'upstream',
|
|
||||||
);
|
|
||||||
|
|
||||||
expect(
|
|
||||||
rollDev(
|
|
||||||
usage: usageString,
|
|
||||||
argResults: fakeArgResults,
|
|
||||||
stdio: stdio,
|
|
||||||
repository: framework,
|
|
||||||
),
|
|
||||||
true,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
stdio.stdout,
|
|
||||||
contains(RegExp(r'Publishing Flutter \d+\.\d+\.\d+-\d+\.\d+\.pre \(')),
|
|
||||||
);
|
|
||||||
|
|
||||||
final Version finalVersion = framework.flutterVersion();
|
|
||||||
expect(
|
|
||||||
initialVersion.toString() != finalVersion.toString(),
|
|
||||||
true,
|
|
||||||
reason: 'initialVersion = $initialVersion; finalVersion = $finalVersion',
|
|
||||||
);
|
|
||||||
expect(finalVersion.n, 0);
|
|
||||||
expect(finalVersion.commits, null);
|
|
||||||
});
|
|
||||||
}, onPlatform: <String, dynamic>{
|
|
||||||
'windows': const Skip('Flutter Conductor only supported on macos/linux'),
|
|
||||||
});
|
|
||||||
}
|
|
@ -20,7 +20,8 @@ void main() {
|
|||||||
const String commit = 'abcde012345';
|
const String commit = 'abcde012345';
|
||||||
const String remote = 'origin';
|
const String remote = 'origin';
|
||||||
const String lastVersion = '1.2.0-0.0.pre';
|
const String lastVersion = '1.2.0-0.0.pre';
|
||||||
const String nextVersion = '1.2.0-1.0.pre';
|
const String nextVersion = '1.2.0-2.0.pre';
|
||||||
|
const String candidateBranch = 'flutter-1.2-candidate.2';
|
||||||
const String checkoutsParentDirectory = '/path/to/directory/';
|
const String checkoutsParentDirectory = '/path/to/directory/';
|
||||||
FakeArgResults fakeArgResults;
|
FakeArgResults fakeArgResults;
|
||||||
MemoryFileSystem fileSystem;
|
MemoryFileSystem fileSystem;
|
||||||
@ -45,37 +46,20 @@ void main() {
|
|||||||
repo = FrameworkRepository(checkouts);
|
repo = FrameworkRepository(checkouts);
|
||||||
});
|
});
|
||||||
|
|
||||||
test('returns false if level not provided', () {
|
test('throws Exception if level not provided', () {
|
||||||
fakeArgResults = FakeArgResults(
|
fakeArgResults = FakeArgResults(
|
||||||
level: null,
|
level: null,
|
||||||
commit: commit,
|
candidateBranch: candidateBranch,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
rollDev(
|
() => rollDev(
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
usage: usage,
|
usage: usage,
|
||||||
),
|
),
|
||||||
false,
|
throwsExceptionWith(usage),
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
test('returns false if commit not provided', () {
|
|
||||||
fakeArgResults = FakeArgResults(
|
|
||||||
level: level,
|
|
||||||
commit: null,
|
|
||||||
remote: remote,
|
|
||||||
);
|
|
||||||
expect(
|
|
||||||
rollDev(
|
|
||||||
argResults: fakeArgResults,
|
|
||||||
repository: repo,
|
|
||||||
stdio: stdio,
|
|
||||||
usage: usage,
|
|
||||||
),
|
|
||||||
false,
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -109,24 +93,18 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
fakeArgResults = FakeArgResults(
|
fakeArgResults = FakeArgResults(
|
||||||
level: level,
|
level: level,
|
||||||
commit: commit,
|
candidateBranch: candidateBranch,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
);
|
);
|
||||||
Exception exception;
|
expect(
|
||||||
try {
|
() => rollDev(
|
||||||
rollDev(
|
|
||||||
argResults: fakeArgResults,
|
argResults: fakeArgResults,
|
||||||
repository: repo,
|
repository: repo,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
usage: usage,
|
usage: usage,
|
||||||
|
),
|
||||||
|
throwsExceptionWith('Your git repository is not clean.'),
|
||||||
);
|
);
|
||||||
} on Exception catch (e) {
|
|
||||||
exception = e;
|
|
||||||
}
|
|
||||||
const String pattern = r'Your git repository is not clean. Try running '
|
|
||||||
'"git clean -fd". Warning, this will delete files! Run with -n to find '
|
|
||||||
'out which ones.';
|
|
||||||
expect(exception?.toString(), contains(pattern));
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('does not reset or tag if --just-print is specified', () {
|
test('does not reset or tag if --just-print is specified', () {
|
||||||
@ -165,13 +143,13 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'rev-parse',
|
'rev-parse',
|
||||||
commit,
|
candidateBranch,
|
||||||
], stdout: commit),
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'describe',
|
'describe',
|
||||||
'--match',
|
'--match',
|
||||||
'*.*.*-*.*.pre',
|
'*.*.*',
|
||||||
'--exact-match',
|
'--exact-match',
|
||||||
'--tags',
|
'--tags',
|
||||||
'refs/remotes/$remote/dev',
|
'refs/remotes/$remote/dev',
|
||||||
@ -185,7 +163,7 @@ void main() {
|
|||||||
|
|
||||||
fakeArgResults = FakeArgResults(
|
fakeArgResults = FakeArgResults(
|
||||||
level: level,
|
level: level,
|
||||||
commit: commit,
|
candidateBranch: candidateBranch,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
justPrint: true,
|
justPrint: true,
|
||||||
);
|
);
|
||||||
@ -237,13 +215,13 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'rev-parse',
|
'rev-parse',
|
||||||
commit,
|
candidateBranch,
|
||||||
], stdout: commit),
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'describe',
|
'describe',
|
||||||
'--match',
|
'--match',
|
||||||
'*.*.*-*.*.pre',
|
'*.*.*',
|
||||||
'--exact-match',
|
'--exact-match',
|
||||||
'--tags',
|
'--tags',
|
||||||
'refs/remotes/$remote/dev',
|
'refs/remotes/$remote/dev',
|
||||||
@ -268,7 +246,7 @@ void main() {
|
|||||||
|
|
||||||
fakeArgResults = FakeArgResults(
|
fakeArgResults = FakeArgResults(
|
||||||
level: level,
|
level: level,
|
||||||
commit: commit,
|
candidateBranch: candidateBranch,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
skipTagging: true,
|
skipTagging: true,
|
||||||
);
|
);
|
||||||
@ -319,13 +297,13 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'rev-parse',
|
'rev-parse',
|
||||||
commit,
|
candidateBranch,
|
||||||
], stdout: commit),
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'describe',
|
'describe',
|
||||||
'--match',
|
'--match',
|
||||||
'*.*.*-*.*.pre',
|
'*.*.*',
|
||||||
'--exact-match',
|
'--exact-match',
|
||||||
'--tags',
|
'--tags',
|
||||||
'refs/remotes/$remote/dev',
|
'refs/remotes/$remote/dev',
|
||||||
@ -339,7 +317,7 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
fakeArgResults = FakeArgResults(
|
fakeArgResults = FakeArgResults(
|
||||||
level: level,
|
level: level,
|
||||||
commit: commit,
|
candidateBranch: candidateBranch,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
justPrint: true,
|
justPrint: true,
|
||||||
);
|
);
|
||||||
@ -394,13 +372,13 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'rev-parse',
|
'rev-parse',
|
||||||
commit,
|
candidateBranch,
|
||||||
], stdout: commit),
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'describe',
|
'describe',
|
||||||
'--match',
|
'--match',
|
||||||
'*.*.*-*.*.pre',
|
'*.*.*',
|
||||||
'--exact-match',
|
'--exact-match',
|
||||||
'--tags',
|
'--tags',
|
||||||
'refs/remotes/$remote/dev',
|
'refs/remotes/$remote/dev',
|
||||||
@ -421,7 +399,7 @@ void main() {
|
|||||||
|
|
||||||
fakeArgResults = FakeArgResults(
|
fakeArgResults = FakeArgResults(
|
||||||
level: level,
|
level: level,
|
||||||
commit: commit,
|
candidateBranch: candidateBranch,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
);
|
);
|
||||||
const String errorMessage = 'The previous dev tag $lastVersion is not a '
|
const String errorMessage = 'The previous dev tag $lastVersion is not a '
|
||||||
@ -473,13 +451,13 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'rev-parse',
|
'rev-parse',
|
||||||
commit,
|
candidateBranch,
|
||||||
], stdout: commit),
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'describe',
|
'describe',
|
||||||
'--match',
|
'--match',
|
||||||
'*.*.*-*.*.pre',
|
'*.*.*',
|
||||||
'--exact-match',
|
'--exact-match',
|
||||||
'--tags',
|
'--tags',
|
||||||
'refs/remotes/$remote/dev',
|
'refs/remotes/$remote/dev',
|
||||||
@ -517,7 +495,7 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
fakeArgResults = FakeArgResults(
|
fakeArgResults = FakeArgResults(
|
||||||
level: level,
|
level: level,
|
||||||
commit: commit,
|
candidateBranch: candidateBranch,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
skipTagging: true,
|
skipTagging: true,
|
||||||
);
|
);
|
||||||
@ -568,13 +546,13 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'rev-parse',
|
'rev-parse',
|
||||||
commit,
|
candidateBranch,
|
||||||
], stdout: commit),
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'describe',
|
'describe',
|
||||||
'--match',
|
'--match',
|
||||||
'*.*.*-*.*.pre',
|
'*.*.*',
|
||||||
'--exact-match',
|
'--exact-match',
|
||||||
'--tags',
|
'--tags',
|
||||||
'refs/remotes/$remote/dev',
|
'refs/remotes/$remote/dev',
|
||||||
@ -617,7 +595,7 @@ void main() {
|
|||||||
]);
|
]);
|
||||||
fakeArgResults = FakeArgResults(
|
fakeArgResults = FakeArgResults(
|
||||||
level: level,
|
level: level,
|
||||||
commit: commit,
|
candidateBranch: candidateBranch,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
);
|
);
|
||||||
expect(
|
expect(
|
||||||
@ -667,13 +645,13 @@ void main() {
|
|||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'rev-parse',
|
'rev-parse',
|
||||||
commit,
|
candidateBranch,
|
||||||
], stdout: commit),
|
], stdout: commit),
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'describe',
|
'describe',
|
||||||
'--match',
|
'--match',
|
||||||
'*.*.*-*.*.pre',
|
'*.*.*',
|
||||||
'--exact-match',
|
'--exact-match',
|
||||||
'--tags',
|
'--tags',
|
||||||
'refs/remotes/$remote/dev',
|
'refs/remotes/$remote/dev',
|
||||||
@ -711,7 +689,7 @@ void main() {
|
|||||||
|
|
||||||
fakeArgResults = FakeArgResults(
|
fakeArgResults = FakeArgResults(
|
||||||
level: level,
|
level: level,
|
||||||
commit: commit,
|
candidateBranch: candidateBranch,
|
||||||
remote: remote,
|
remote: remote,
|
||||||
force: true,
|
force: true,
|
||||||
);
|
);
|
||||||
|
@ -122,6 +122,8 @@ void main() {
|
|||||||
const String revision3 = '123abc';
|
const String revision3 = '123abc';
|
||||||
const String previousDartRevision = '171876a4e6cf56ee6da1f97d203926bd7afda7ef';
|
const String previousDartRevision = '171876a4e6cf56ee6da1f97d203926bd7afda7ef';
|
||||||
const String nextDartRevision = 'f6c91128be6b77aef8351e1e3a9d07c85bc2e46e';
|
const String nextDartRevision = 'f6c91128be6b77aef8351e1e3a9d07c85bc2e46e';
|
||||||
|
const String previousVersion = '1.2.0-1.0.pre';
|
||||||
|
const String nextVersion = '1.2.0-3.0.pre';
|
||||||
|
|
||||||
final Directory engine = fileSystem.directory(checkoutsParentDirectory)
|
final Directory engine = fileSystem.directory(checkoutsParentDirectory)
|
||||||
.childDirectory('flutter_conductor_checkouts')
|
.childDirectory('flutter_conductor_checkouts')
|
||||||
@ -182,6 +184,7 @@ void main() {
|
|||||||
stdout: revision2,
|
stdout: revision2,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
final List<FakeCommand> frameworkCommands = <FakeCommand>[
|
final List<FakeCommand> frameworkCommands = <FakeCommand>[
|
||||||
FakeCommand(
|
FakeCommand(
|
||||||
command: <String>[
|
command: <String>[
|
||||||
@ -219,11 +222,23 @@ void main() {
|
|||||||
'cherrypicks-$candidateBranch',
|
'cherrypicks-$candidateBranch',
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
|
const FakeCommand(
|
||||||
|
command: <String>[
|
||||||
|
'git',
|
||||||
|
'describe',
|
||||||
|
'--match',
|
||||||
|
'*.*.*',
|
||||||
|
'--tags',
|
||||||
|
'refs/remotes/upstream/$candidateBranch',
|
||||||
|
],
|
||||||
|
stdout: '$previousVersion-42-gabc123',
|
||||||
|
),
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||||
stdout: revision3,
|
stdout: revision3,
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
|
|
||||||
final CommandRunner<void> runner = createRunner(
|
final CommandRunner<void> runner = createRunner(
|
||||||
commands: <FakeCommand>[
|
commands: <FakeCommand>[
|
||||||
const FakeCommand(
|
const FakeCommand(
|
||||||
@ -254,6 +269,8 @@ void main() {
|
|||||||
stateFilePath,
|
stateFilePath,
|
||||||
'--$kDartRevisionOption',
|
'--$kDartRevisionOption',
|
||||||
nextDartRevision,
|
nextDartRevision,
|
||||||
|
'--$kIncrementOption',
|
||||||
|
'm',
|
||||||
]);
|
]);
|
||||||
|
|
||||||
final File stateFile = fileSystem.file(stateFilePath);
|
final File stateFile = fileSystem.file(stateFilePath);
|
||||||
@ -265,12 +282,13 @@ void main() {
|
|||||||
|
|
||||||
expect(state.isInitialized(), true);
|
expect(state.isInitialized(), true);
|
||||||
expect(state.releaseChannel, releaseChannel);
|
expect(state.releaseChannel, releaseChannel);
|
||||||
|
expect(state.releaseVersion, nextVersion);
|
||||||
expect(state.engine.candidateBranch, candidateBranch);
|
expect(state.engine.candidateBranch, candidateBranch);
|
||||||
expect(state.engine.startingGitHead, revision2);
|
expect(state.engine.startingGitHead, revision2);
|
||||||
expect(state.engine.dartRevision, nextDartRevision);
|
expect(state.engine.dartRevision, nextDartRevision);
|
||||||
expect(state.framework.candidateBranch, candidateBranch);
|
expect(state.framework.candidateBranch, candidateBranch);
|
||||||
expect(state.framework.startingGitHead, revision3);
|
expect(state.framework.startingGitHead, revision3);
|
||||||
expect(state.lastPhase, ReleasePhase.INITIALIZE);
|
expect(state.currentPhase, ReleasePhase.APPLY_ENGINE_CHERRYPICKS);
|
||||||
expect(state.conductorVersion, revision);
|
expect(state.conductorVersion, revision);
|
||||||
});
|
});
|
||||||
}, onPlatform: <String, dynamic>{
|
}, onPlatform: <String, dynamic>{
|
||||||
|
@ -44,29 +44,26 @@ void main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
test('successfully increments z', () {
|
test('successfully increments z', () {
|
||||||
const String level = 'm';
|
const String level = 'z';
|
||||||
|
|
||||||
Version version = Version.fromString('1.0.0-0.0.pre');
|
Version version = Version.fromString('1.0.0');
|
||||||
expect(Version.increment(version, level).toString(), '1.0.0-1.0.pre');
|
expect(Version.increment(version, level).toString(), '1.0.1');
|
||||||
|
|
||||||
version = Version.fromString('10.20.0-40.50.pre');
|
version = Version.fromString('10.20.0');
|
||||||
expect(Version.increment(version, level).toString(), '10.20.0-41.0.pre');
|
expect(Version.increment(version, level).toString(), '10.20.1');
|
||||||
|
|
||||||
version = Version.fromString('1.18.0-3.0.pre');
|
version = Version.fromString('1.18.3');
|
||||||
expect(Version.increment(version, level).toString(), '1.18.0-4.0.pre');
|
expect(Version.increment(version, level).toString(), '1.18.4');
|
||||||
});
|
});
|
||||||
|
|
||||||
test('successfully increments m', () {
|
test('does not support incrementing m', () {
|
||||||
const String level = 'm';
|
const String level = 'm';
|
||||||
|
|
||||||
Version version = Version.fromString('1.0.0-0.0.pre');
|
final Version version = Version.fromString('1.0.0-0.0.pre');
|
||||||
expect(Version.increment(version, level).toString(), '1.0.0-1.0.pre');
|
expect(
|
||||||
|
() => Version.increment(version, level).toString(),
|
||||||
version = Version.fromString('10.20.0-40.50.pre');
|
throwsAssertionWith("Do not increment 'm' via Version.increment"),
|
||||||
expect(Version.increment(version, level).toString(), '10.20.0-41.0.pre');
|
);
|
||||||
|
|
||||||
version = Version.fromString('1.18.0-3.0.pre');
|
|
||||||
expect(Version.increment(version, level).toString(), '1.18.0-4.0.pre');
|
|
||||||
});
|
});
|
||||||
|
|
||||||
test('successfully increments n', () {
|
test('successfully increments n', () {
|
||||||
|
Loading…
Reference in New Issue
Block a user