mirror of
https://github.com/flutter/flutter
synced 2024-08-27 20:10:11 +00:00
[flutter_conductor] Add "start", "status", "clean" commands to conductor release tool (#80528)
This commit is contained in:
parent
dc40e23893
commit
4a7f280687
|
@ -40,6 +40,8 @@ analyzer:
|
|||
unnecessary_null_comparison: ignore
|
||||
exclude:
|
||||
- "bin/cache/**"
|
||||
# Ignore protoc generated files
|
||||
- "dev/tools/lib/proto/*"
|
||||
|
||||
linter:
|
||||
rules:
|
||||
|
|
|
@ -2,18 +2,18 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// Rolls the dev channel.
|
||||
// Only tested on Linux.
|
||||
//
|
||||
// See: https://github.com/flutter/flutter/wiki/Release-process
|
||||
|
||||
import 'dart:io' as io;
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:dev_tools/clean.dart';
|
||||
import 'package:dev_tools/codesign.dart';
|
||||
import 'package:dev_tools/globals.dart';
|
||||
import 'package:dev_tools/roll_dev.dart';
|
||||
import 'package:dev_tools/repository.dart';
|
||||
import 'package:dev_tools/start.dart';
|
||||
import 'package:dev_tools/status.dart';
|
||||
import 'package:dev_tools/stdio.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/local.dart';
|
||||
|
@ -54,6 +54,16 @@ Future<void> main(List<String> args) async {
|
|||
checkouts: checkouts,
|
||||
flutterRoot: localFlutterRoot,
|
||||
),
|
||||
StatusCommand(
|
||||
checkouts: checkouts,
|
||||
),
|
||||
StartCommand(
|
||||
checkouts: checkouts,
|
||||
flutterRoot: localFlutterRoot,
|
||||
),
|
||||
CleanCommand(
|
||||
checkouts: checkouts,
|
||||
),
|
||||
].forEach(runner.addCommand);
|
||||
|
||||
if (!assertsEnabled()) {
|
||||
|
@ -63,8 +73,8 @@ Future<void> main(List<String> args) async {
|
|||
|
||||
try {
|
||||
await runner.run(args);
|
||||
} on Exception catch (e) {
|
||||
stdio.printError(e.toString());
|
||||
} on Exception catch (e, stacktrace) {
|
||||
stdio.printError('$e\n\n$stacktrace');
|
||||
io.exit(1);
|
||||
}
|
||||
}
|
||||
|
|
75
dev/tools/lib/clean.dart
Normal file
75
dev/tools/lib/clean.dart
Normal file
|
@ -0,0 +1,75 @@
|
|||
// 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:args/command_runner.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import './globals.dart';
|
||||
import './repository.dart';
|
||||
import './state.dart';
|
||||
import './stdio.dart';
|
||||
|
||||
const String kYesFlag = 'yes';
|
||||
const String kStateOption = 'state-file';
|
||||
|
||||
/// Command to clean up persistent state file.
|
||||
///
|
||||
/// If the release was not completed, this command will abort the release.
|
||||
class CleanCommand extends Command<void> {
|
||||
CleanCommand({
|
||||
@required this.checkouts,
|
||||
}) : platform = checkouts.platform,
|
||||
fileSystem = checkouts.fileSystem,
|
||||
stdio = checkouts.stdio {
|
||||
final String defaultPath = defaultStateFilePath(platform);
|
||||
argParser.addFlag(
|
||||
kYesFlag,
|
||||
help: 'Override confirmation checks.',
|
||||
);
|
||||
argParser.addOption(
|
||||
kStateOption,
|
||||
defaultsTo: defaultPath,
|
||||
help: 'Path to persistent state file. Defaults to $defaultPath',
|
||||
);
|
||||
}
|
||||
|
||||
final Checkouts checkouts;
|
||||
final FileSystem fileSystem;
|
||||
final Platform platform;
|
||||
final Stdio stdio;
|
||||
|
||||
@override
|
||||
String get name => 'clean';
|
||||
|
||||
@override
|
||||
String get description => 'Cleanup persistent state file. '
|
||||
'This will abort a work in progress release.';
|
||||
|
||||
@override
|
||||
void run() {
|
||||
final File stateFile = checkouts.fileSystem.file(argResults[kStateOption]);
|
||||
if (!stateFile.existsSync()) {
|
||||
throw ConductorException(
|
||||
'No persistent state file found at ${stateFile.path}!');
|
||||
}
|
||||
|
||||
if (!(argResults[kYesFlag] as bool)) {
|
||||
stdio.printStatus(
|
||||
'Are you sure you want to clean up the persistent state file at\n'
|
||||
'${stateFile.path} (y/n)?',
|
||||
);
|
||||
final String response = stdio.readLineSync();
|
||||
|
||||
// Only proceed if the first character of stdin is 'y' or 'Y'
|
||||
if (response.isEmpty || response[0].toLowerCase() != 'y') {
|
||||
stdio.printStatus('Aborting clean operation.');
|
||||
return;
|
||||
}
|
||||
}
|
||||
stdio.printStatus('Deleting persistent state file ${stateFile.path}...');
|
||||
stateFile.deleteSync();
|
||||
}
|
||||
}
|
|
@ -33,11 +33,15 @@ class CodesignCommand extends Command<void> {
|
|||
CodesignCommand({
|
||||
@required this.checkouts,
|
||||
@required this.flutterRoot,
|
||||
FrameworkRepository framework,
|
||||
}) : assert(flutterRoot != null),
|
||||
fileSystem = checkouts.fileSystem,
|
||||
platform = checkouts.platform,
|
||||
stdio = checkouts.stdio,
|
||||
processManager = checkouts.processManager {
|
||||
if (framework != null) {
|
||||
_framework = framework;
|
||||
}
|
||||
argParser.addFlag(
|
||||
kVerify,
|
||||
help:
|
||||
|
@ -71,13 +75,12 @@ class CodesignCommand extends Command<void> {
|
|||
final Directory flutterRoot;
|
||||
|
||||
FrameworkRepository _framework;
|
||||
FrameworkRepository get framework => _framework ??= FrameworkRepository.localRepoAsUpstream(
|
||||
checkouts,
|
||||
upstreamPath: flutterRoot.path,
|
||||
);
|
||||
|
||||
@visibleForTesting
|
||||
set framework(FrameworkRepository framework) => _framework = framework;
|
||||
FrameworkRepository get framework {
|
||||
return _framework ??= FrameworkRepository.localRepoAsUpstream(
|
||||
checkouts,
|
||||
upstreamPath: flutterRoot.path,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => 'codesign';
|
||||
|
@ -102,15 +105,19 @@ class CodesignCommand extends Command<void> {
|
|||
|
||||
String revision;
|
||||
if (argResults.wasParsed(kRevision)) {
|
||||
stdio.printError('Warning! When providing an arbitrary revision, the contents of the cache may not');
|
||||
stdio.printError('match the expected binaries in the conductor tool. It is preferred to check out');
|
||||
stdio.printError('the desired revision and run that version of the conductor.\n');
|
||||
stdio.printError(
|
||||
'Warning! When providing an arbitrary revision, the contents of the cache may not');
|
||||
stdio.printError(
|
||||
'match the expected binaries in the conductor tool. It is preferred to check out');
|
||||
stdio.printError(
|
||||
'the desired revision and run that version of the conductor.\n');
|
||||
revision = argResults[kRevision] as String;
|
||||
} else {
|
||||
revision = (processManager.runSync(
|
||||
<String>['git', 'rev-parse', 'HEAD'],
|
||||
workingDirectory: framework.checkoutDirectory.path,
|
||||
).stdout as String).trim();
|
||||
).stdout as String)
|
||||
.trim();
|
||||
assert(revision.isNotEmpty);
|
||||
}
|
||||
|
||||
|
@ -158,7 +165,10 @@ class CodesignCommand extends Command<void> {
|
|||
'dart-sdk/bin/dart',
|
||||
'dart-sdk/bin/dartaotruntime',
|
||||
'dart-sdk/bin/utils/gen_snapshot',
|
||||
].map((String relativePath) => fileSystem.path.join(framework.cacheDirectory, relativePath)).toList();
|
||||
]
|
||||
.map((String relativePath) =>
|
||||
fileSystem.path.join(framework.cacheDirectory, relativePath))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Binaries that are only expected to be codesigned.
|
||||
|
@ -178,7 +188,10 @@ class CodesignCommand extends Command<void> {
|
|||
'artifacts/engine/ios/Flutter.xcframework/ios-armv7_arm64/Flutter.framework/Flutter',
|
||||
'artifacts/engine/ios/Flutter.xcframework/ios-x86_64-simulator/Flutter.framework/Flutter',
|
||||
'artifacts/ios-deploy/ios-deploy',
|
||||
].map((String relativePath) => fileSystem.path.join(framework.cacheDirectory, relativePath)).toList();
|
||||
]
|
||||
.map((String relativePath) =>
|
||||
fileSystem.path.join(framework.cacheDirectory, relativePath))
|
||||
.toList();
|
||||
}
|
||||
|
||||
/// Verify the existence of all expected binaries in cache.
|
||||
|
@ -197,19 +210,27 @@ class CodesignCommand extends Command<void> {
|
|||
} else if (binariesWithoutEntitlements.contains(binaryPath)) {
|
||||
foundFiles.add(binaryPath);
|
||||
} else {
|
||||
throw ConductorException('Found unexpected binary in cache: $binaryPath');
|
||||
throw ConductorException(
|
||||
'Found unexpected binary in cache: $binaryPath');
|
||||
}
|
||||
}
|
||||
|
||||
final List<String> allExpectedFiles = binariesWithEntitlements + binariesWithoutEntitlements;
|
||||
final List<String> allExpectedFiles =
|
||||
binariesWithEntitlements + binariesWithoutEntitlements;
|
||||
if (foundFiles.length < allExpectedFiles.length) {
|
||||
final List<String> unfoundFiles = allExpectedFiles.where(
|
||||
(String file) => !foundFiles.contains(file),
|
||||
).toList();
|
||||
stdio.printError('Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n');
|
||||
stdio.printError('If this commit is removing binaries from the cache, this test should be fixed by');
|
||||
stdio.printError('removing the relevant entry from either the `binariesWithEntitlements` or');
|
||||
stdio.printError('`binariesWithoutEntitlements` getters in dev/tools/lib/codesign.dart.');
|
||||
final List<String> unfoundFiles = allExpectedFiles
|
||||
.where(
|
||||
(String file) => !foundFiles.contains(file),
|
||||
)
|
||||
.toList();
|
||||
stdio.printError(
|
||||
'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n');
|
||||
stdio.printError(
|
||||
'If this commit is removing binaries from the cache, this test should be fixed by');
|
||||
stdio.printError(
|
||||
'removing the relevant entry from either the `binariesWithEntitlements` or');
|
||||
stdio.printError(
|
||||
'`binariesWithoutEntitlements` getters in dev/tools/lib/codesign.dart.');
|
||||
throw ConductorException('Did not find all expected binaries!');
|
||||
}
|
||||
|
||||
|
@ -275,13 +296,15 @@ class CodesignCommand extends Command<void> {
|
|||
}
|
||||
|
||||
if (unexpectedBinaries.isNotEmpty) {
|
||||
stdio.printError('Found ${unexpectedBinaries.length} unexpected binaries in the cache:');
|
||||
stdio.printError(
|
||||
'Found ${unexpectedBinaries.length} unexpected binaries in the cache:');
|
||||
unexpectedBinaries.forEach(print);
|
||||
}
|
||||
|
||||
// Finally, exit on any invalid state
|
||||
if (unsignedBinaries.isNotEmpty) {
|
||||
throw ConductorException('Test failed because unsigned binaries detected.');
|
||||
throw ConductorException(
|
||||
'Test failed because unsigned binaries detected.');
|
||||
}
|
||||
|
||||
if (wrongEntitlementBinaries.isNotEmpty) {
|
||||
|
@ -291,7 +314,8 @@ class CodesignCommand extends Command<void> {
|
|||
}
|
||||
|
||||
if (unexpectedBinaries.isNotEmpty) {
|
||||
throw ConductorException('Test failed because unexpected binaries found in the cache.');
|
||||
throw ConductorException(
|
||||
'Test failed because unexpected binaries found in the cache.');
|
||||
}
|
||||
|
||||
stdio.printStatus(
|
||||
|
@ -300,6 +324,7 @@ class CodesignCommand extends Command<void> {
|
|||
}
|
||||
|
||||
List<String> _allBinaryPaths;
|
||||
|
||||
/// Find every binary file in the given [rootDirectory].
|
||||
List<String> findBinaryPaths(String rootDirectory) {
|
||||
if (_allBinaryPaths != null) {
|
||||
|
@ -356,7 +381,8 @@ class CodesignCommand extends Command<void> {
|
|||
bool passes = true;
|
||||
final String output = entitlementResult.stdout as String;
|
||||
for (final String entitlement in expectedEntitlements) {
|
||||
final bool entitlementExpected = binariesWithEntitlements.contains(binaryPath);
|
||||
final bool entitlementExpected =
|
||||
binariesWithEntitlements.contains(binaryPath);
|
||||
if (output.contains(entitlement) != entitlementExpected) {
|
||||
stdio.printError(
|
||||
'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} '
|
||||
|
|
|
@ -19,6 +19,7 @@ class Git {
|
|||
List<String> args,
|
||||
String explanation, {
|
||||
@required String workingDirectory,
|
||||
bool allowFailures = false,
|
||||
}) {
|
||||
final ProcessResult result = _run(args, workingDirectory);
|
||||
if (result.exitCode == 0) {
|
||||
|
@ -68,6 +69,15 @@ class Git {
|
|||
message.writeln('stdout from git:\n${result.stdout}\n');
|
||||
if ((result.stderr as String).isNotEmpty)
|
||||
message.writeln('stderr from git:\n${result.stderr}\n');
|
||||
throw Exception(message);
|
||||
throw GitException(message.toString());
|
||||
}
|
||||
}
|
||||
|
||||
class GitException implements Exception {
|
||||
GitException(this.message);
|
||||
|
||||
final String message;
|
||||
|
||||
@override
|
||||
String toString() => 'Exception: $message';
|
||||
}
|
||||
|
|
|
@ -2,20 +2,15 @@
|
|||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:args/args.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/local.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
const String kIncrement = 'increment';
|
||||
const String kCommit = 'commit';
|
||||
const String kRemoteName = 'remote';
|
||||
const String kJustPrint = 'just-print';
|
||||
const String kYes = 'yes';
|
||||
const String kForce = 'force';
|
||||
const String kSkipTagging = 'skip-tagging';
|
||||
|
||||
const String kUpstreamRemote = 'https://github.com/flutter/flutter.git';
|
||||
|
||||
const String gsutilBinary = 'gsutil.py';
|
||||
|
||||
const List<String> kReleaseChannels = <String>[
|
||||
'stable',
|
||||
'beta',
|
||||
|
@ -23,6 +18,12 @@ const List<String> kReleaseChannels = <String>[
|
|||
'master',
|
||||
];
|
||||
|
||||
const String kReleaseDocumentationUrl = 'https://github.com/flutter/flutter/wiki/Flutter-Cherrypick-Process';
|
||||
|
||||
final RegExp releaseCandidateBranchRegex = RegExp(
|
||||
r'flutter-(\d+)\.(\d+)-candidate\.(\d+)',
|
||||
);
|
||||
|
||||
/// Cast a dynamic to String and trim.
|
||||
String stdoutToString(dynamic input) {
|
||||
final String str = input as String;
|
||||
|
@ -86,3 +87,64 @@ bool assertsEnabled() {
|
|||
}());
|
||||
return assertsEnabled;
|
||||
}
|
||||
|
||||
/// Either return the value from [env] or fall back to [argResults].
|
||||
///
|
||||
/// If the key does not exist in either the environment or CLI args, throws a
|
||||
/// [ConductorException].
|
||||
///
|
||||
/// The environment is favored over CLI args since the latter can have a default
|
||||
/// value, which the environment should be able to override.
|
||||
String getValueFromEnvOrArgs(
|
||||
String name,
|
||||
ArgResults argResults,
|
||||
Map<String, String> env,
|
||||
) {
|
||||
final String envName = fromArgToEnvName(name);
|
||||
if (env[envName] != null ) {
|
||||
return env[envName];
|
||||
}
|
||||
final String argValue = argResults[name] as String;
|
||||
if (argValue != null) {
|
||||
return argValue;
|
||||
}
|
||||
|
||||
throw ConductorException(
|
||||
'Expected either the CLI arg --$name or the environment variable $envName '
|
||||
'to be provided!');
|
||||
}
|
||||
|
||||
/// Return multiple values from the environment or fall back to [argResults].
|
||||
///
|
||||
/// Values read from an environment variable are assumed to be comma-delimited.
|
||||
///
|
||||
/// If the key does not exist in either the CLI args or environment, throws a
|
||||
/// [ConductorException].
|
||||
///
|
||||
/// The environment is favored over CLI args since the latter can have a default
|
||||
/// value, which the environment should be able to override.
|
||||
List<String> getValuesFromEnvOrArgs(
|
||||
String name,
|
||||
ArgResults argResults,
|
||||
Map<String, String> env,
|
||||
) {
|
||||
final String envName = fromArgToEnvName(name);
|
||||
if (env[envName] != null && env[envName] != '') {
|
||||
return env[envName].split(',');
|
||||
}
|
||||
final List<String> argValues = argResults[name] as List<String>;
|
||||
if (argValues != null) {
|
||||
return argValues;
|
||||
}
|
||||
|
||||
throw ConductorException(
|
||||
'Expected either the CLI arg --$name or the environment variable $envName '
|
||||
'to be provided!');
|
||||
}
|
||||
|
||||
/// Translate CLI arg names to env variable names.
|
||||
///
|
||||
/// For example, 'state-file' -> 'STATE_FILE'.
|
||||
String fromArgToEnvName(String argName) {
|
||||
return argName.toUpperCase().replaceAll(r'-', r'_');
|
||||
}
|
||||
|
|
8
dev/tools/lib/proto/README.md
Normal file
8
dev/tools/lib/proto/README.md
Normal file
|
@ -0,0 +1,8 @@
|
|||
## Flutter Conductor Protocol Buffers
|
||||
|
||||
This directory contains [conductor_state.proto](./conductor_state.proto), which
|
||||
defines the persistent state file the conductor creates. After changes to this
|
||||
file, you must run the [compile_proto.sh](./compile_proto.sh) script in this
|
||||
directory, which will re-generate the rest of the Dart files in this directory,
|
||||
format them, and prepend the license comment from
|
||||
[license_header.txt](./license_header.txt).
|
45
dev/tools/lib/proto/compile_proto.sh
Executable file
45
dev/tools/lib/proto/compile_proto.sh
Executable file
|
@ -0,0 +1,45 @@
|
|||
#!/usr/bin/env bash
|
||||
# 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.
|
||||
|
||||
# //flutter/dev/tools/lib/proto
|
||||
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
|
||||
DARTFMT="$DIR/../../../../bin/cache/dart-sdk/bin/dartfmt"
|
||||
|
||||
# Ensure dart-sdk is cached
|
||||
"$DIR/../../../../bin/dart" --version
|
||||
|
||||
if ! type protoc >/dev/null 2>&1; then
|
||||
PROTOC_LINK='https://grpc.io/docs/protoc-installation/'
|
||||
echo "Error! \"protoc\" binary required on path."
|
||||
echo "See $PROTOC_LINK for more information."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if ! type dart >/dev/null 2>&1; then
|
||||
echo "Error! \"dart\" binary required on path."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Pin protoc-gen-dart to pre-nullsafe version.
|
||||
dart pub global activate protoc_plugin 19.3.1
|
||||
|
||||
protoc --dart_out="$DIR" --proto_path="$DIR" "$DIR/conductor_state.proto"
|
||||
|
||||
for SOURCE_FILE in $(ls "$DIR"/*.pb*.dart); do
|
||||
# Format in place file
|
||||
"$DARTFMT" --overwrite --line-length 120 "$SOURCE_FILE"
|
||||
|
||||
# Create temp copy with the license header prepended
|
||||
cp license_header.txt "${SOURCE_FILE}.tmp"
|
||||
|
||||
# Add an extra newline required by analysis (analysis also prevents
|
||||
# license_header.txt from having the trailing newline)
|
||||
echo '' >> "${SOURCE_FILE}.tmp"
|
||||
|
||||
cat "$SOURCE_FILE" >> "${SOURCE_FILE}.tmp"
|
||||
|
||||
# Move temp version (with license) over the original
|
||||
mv "${SOURCE_FILE}.tmp" "$SOURCE_FILE"
|
||||
done
|
538
dev/tools/lib/proto/conductor_state.pb.dart
Normal file
538
dev/tools/lib/proto/conductor_state.pb.dart
Normal file
|
@ -0,0 +1,538 @@
|
|||
// 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.
|
||||
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: conductor_state.proto
|
||||
//
|
||||
// @dart = 2.7
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
||||
|
||||
import 'dart:core' as $core;
|
||||
|
||||
import 'package:fixnum/fixnum.dart' as $fixnum;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
import 'conductor_state.pbenum.dart';
|
||||
|
||||
export 'conductor_state.pbenum.dart';
|
||||
|
||||
class Remote extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Remote',
|
||||
package: const $pb.PackageName(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'conductor_state'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'name')
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'url')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
Remote._() : super();
|
||||
factory Remote({
|
||||
$core.String name,
|
||||
$core.String url,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (name != null) {
|
||||
_result.name = name;
|
||||
}
|
||||
if (url != null) {
|
||||
_result.url = url;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory Remote.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory Remote.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
Remote clone() => Remote()..mergeFromMessage(this);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Remote copyWith(void Function(Remote) updates) =>
|
||||
super.copyWith((message) => updates(message as Remote)) as Remote; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Remote create() => Remote._();
|
||||
Remote createEmptyInstance() => create();
|
||||
static $pb.PbList<Remote> createRepeated() => $pb.PbList<Remote>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Remote getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Remote>(create);
|
||||
static Remote _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get name => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set name($core.String v) {
|
||||
$_setString(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasName() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearName() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get url => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set url($core.String v) {
|
||||
$_setString(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasUrl() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearUrl() => clearField(2);
|
||||
}
|
||||
|
||||
class Cherrypick extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Cherrypick',
|
||||
package: const $pb.PackageName(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'conductor_state'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'trunkRevision',
|
||||
protoName: 'trunkRevision')
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'appliedRevision',
|
||||
protoName: 'appliedRevision')
|
||||
..e<CherrypickState>(
|
||||
3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'state', $pb.PbFieldType.OE,
|
||||
defaultOrMaker: CherrypickState.PENDING, valueOf: CherrypickState.valueOf, enumValues: CherrypickState.values)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
Cherrypick._() : super();
|
||||
factory Cherrypick({
|
||||
$core.String trunkRevision,
|
||||
$core.String appliedRevision,
|
||||
CherrypickState state,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (trunkRevision != null) {
|
||||
_result.trunkRevision = trunkRevision;
|
||||
}
|
||||
if (appliedRevision != null) {
|
||||
_result.appliedRevision = appliedRevision;
|
||||
}
|
||||
if (state != null) {
|
||||
_result.state = state;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory Cherrypick.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory Cherrypick.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
Cherrypick clone() => Cherrypick()..mergeFromMessage(this);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Cherrypick copyWith(void Function(Cherrypick) updates) =>
|
||||
super.copyWith((message) => updates(message as Cherrypick)) as Cherrypick; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Cherrypick create() => Cherrypick._();
|
||||
Cherrypick createEmptyInstance() => create();
|
||||
static $pb.PbList<Cherrypick> createRepeated() => $pb.PbList<Cherrypick>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Cherrypick getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Cherrypick>(create);
|
||||
static Cherrypick _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get trunkRevision => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set trunkRevision($core.String v) {
|
||||
$_setString(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasTrunkRevision() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearTrunkRevision() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get appliedRevision => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set appliedRevision($core.String v) {
|
||||
$_setString(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasAppliedRevision() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearAppliedRevision() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
CherrypickState get state => $_getN(2);
|
||||
@$pb.TagNumber(3)
|
||||
set state(CherrypickState v) {
|
||||
setField(3, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasState() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearState() => clearField(3);
|
||||
}
|
||||
|
||||
class Repository extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'Repository',
|
||||
package: const $pb.PackageName(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'conductor_state'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'candidateBranch',
|
||||
protoName: 'candidateBranch')
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'startingGitHead',
|
||||
protoName: 'startingGitHead')
|
||||
..aOS(3, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'currentGitHead',
|
||||
protoName: 'currentGitHead')
|
||||
..aOS(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'checkoutPath',
|
||||
protoName: 'checkoutPath')
|
||||
..aOM<Remote>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'upstream',
|
||||
subBuilder: Remote.create)
|
||||
..aOM<Remote>(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'mirror',
|
||||
subBuilder: Remote.create)
|
||||
..pc<Cherrypick>(
|
||||
7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'cherrypicks', $pb.PbFieldType.PM,
|
||||
subBuilder: Cherrypick.create)
|
||||
..hasRequiredFields = false;
|
||||
|
||||
Repository._() : super();
|
||||
factory Repository({
|
||||
$core.String candidateBranch,
|
||||
$core.String startingGitHead,
|
||||
$core.String currentGitHead,
|
||||
$core.String checkoutPath,
|
||||
Remote upstream,
|
||||
Remote mirror,
|
||||
$core.Iterable<Cherrypick> cherrypicks,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (candidateBranch != null) {
|
||||
_result.candidateBranch = candidateBranch;
|
||||
}
|
||||
if (startingGitHead != null) {
|
||||
_result.startingGitHead = startingGitHead;
|
||||
}
|
||||
if (currentGitHead != null) {
|
||||
_result.currentGitHead = currentGitHead;
|
||||
}
|
||||
if (checkoutPath != null) {
|
||||
_result.checkoutPath = checkoutPath;
|
||||
}
|
||||
if (upstream != null) {
|
||||
_result.upstream = upstream;
|
||||
}
|
||||
if (mirror != null) {
|
||||
_result.mirror = mirror;
|
||||
}
|
||||
if (cherrypicks != null) {
|
||||
_result.cherrypicks.addAll(cherrypicks);
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory Repository.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory Repository.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
Repository clone() => Repository()..mergeFromMessage(this);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
Repository copyWith(void Function(Repository) updates) =>
|
||||
super.copyWith((message) => updates(message as Repository)) as Repository; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Repository create() => Repository._();
|
||||
Repository createEmptyInstance() => create();
|
||||
static $pb.PbList<Repository> createRepeated() => $pb.PbList<Repository>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static Repository getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<Repository>(create);
|
||||
static Repository _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get candidateBranch => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set candidateBranch($core.String v) {
|
||||
$_setString(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasCandidateBranch() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearCandidateBranch() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get startingGitHead => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set startingGitHead($core.String v) {
|
||||
$_setString(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasStartingGitHead() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearStartingGitHead() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.String get currentGitHead => $_getSZ(2);
|
||||
@$pb.TagNumber(3)
|
||||
set currentGitHead($core.String v) {
|
||||
$_setString(2, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(3)
|
||||
$core.bool hasCurrentGitHead() => $_has(2);
|
||||
@$pb.TagNumber(3)
|
||||
void clearCurrentGitHead() => clearField(3);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.String get checkoutPath => $_getSZ(3);
|
||||
@$pb.TagNumber(4)
|
||||
set checkoutPath($core.String v) {
|
||||
$_setString(3, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasCheckoutPath() => $_has(3);
|
||||
@$pb.TagNumber(4)
|
||||
void clearCheckoutPath() => clearField(4);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
Remote get upstream => $_getN(4);
|
||||
@$pb.TagNumber(5)
|
||||
set upstream(Remote v) {
|
||||
setField(5, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasUpstream() => $_has(4);
|
||||
@$pb.TagNumber(5)
|
||||
void clearUpstream() => clearField(5);
|
||||
@$pb.TagNumber(5)
|
||||
Remote ensureUpstream() => $_ensure(4);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
Remote get mirror => $_getN(5);
|
||||
@$pb.TagNumber(6)
|
||||
set mirror(Remote v) {
|
||||
setField(6, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasMirror() => $_has(5);
|
||||
@$pb.TagNumber(6)
|
||||
void clearMirror() => clearField(6);
|
||||
@$pb.TagNumber(6)
|
||||
Remote ensureMirror() => $_ensure(5);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.List<Cherrypick> get cherrypicks => $_getList(6);
|
||||
}
|
||||
|
||||
class ConductorState extends $pb.GeneratedMessage {
|
||||
static final $pb.BuilderInfo _i = $pb.BuilderInfo(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'ConductorState',
|
||||
package: const $pb.PackageName(
|
||||
const $core.bool.fromEnvironment('protobuf.omit_message_names') ? '' : 'conductor_state'),
|
||||
createEmptyInstance: create)
|
||||
..aOS(1, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'releaseChannel',
|
||||
protoName: 'releaseChannel')
|
||||
..aOS(2, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'releaseVersion',
|
||||
protoName: 'releaseVersion')
|
||||
..aOM<Repository>(4, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'engine',
|
||||
subBuilder: Repository.create)
|
||||
..aOM<Repository>(5, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'framework',
|
||||
subBuilder: Repository.create)
|
||||
..aInt64(6, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'createdDate',
|
||||
protoName: 'createdDate')
|
||||
..aInt64(7, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'lastUpdatedDate',
|
||||
protoName: 'lastUpdatedDate')
|
||||
..pPS(8, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'logs')
|
||||
..e<ReleasePhase>(
|
||||
9, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'lastPhase', $pb.PbFieldType.OE,
|
||||
protoName: 'lastPhase',
|
||||
defaultOrMaker: ReleasePhase.INITIALIZE,
|
||||
valueOf: ReleasePhase.valueOf,
|
||||
enumValues: ReleasePhase.values)
|
||||
..aOS(10, const $core.bool.fromEnvironment('protobuf.omit_field_names') ? '' : 'conductorVersion')
|
||||
..hasRequiredFields = false;
|
||||
|
||||
ConductorState._() : super();
|
||||
factory ConductorState({
|
||||
$core.String releaseChannel,
|
||||
$core.String releaseVersion,
|
||||
Repository engine,
|
||||
Repository framework,
|
||||
$fixnum.Int64 createdDate,
|
||||
$fixnum.Int64 lastUpdatedDate,
|
||||
$core.Iterable<$core.String> logs,
|
||||
ReleasePhase lastPhase,
|
||||
$core.String conductorVersion,
|
||||
}) {
|
||||
final _result = create();
|
||||
if (releaseChannel != null) {
|
||||
_result.releaseChannel = releaseChannel;
|
||||
}
|
||||
if (releaseVersion != null) {
|
||||
_result.releaseVersion = releaseVersion;
|
||||
}
|
||||
if (engine != null) {
|
||||
_result.engine = engine;
|
||||
}
|
||||
if (framework != null) {
|
||||
_result.framework = framework;
|
||||
}
|
||||
if (createdDate != null) {
|
||||
_result.createdDate = createdDate;
|
||||
}
|
||||
if (lastUpdatedDate != null) {
|
||||
_result.lastUpdatedDate = lastUpdatedDate;
|
||||
}
|
||||
if (logs != null) {
|
||||
_result.logs.addAll(logs);
|
||||
}
|
||||
if (lastPhase != null) {
|
||||
_result.lastPhase = lastPhase;
|
||||
}
|
||||
if (conductorVersion != null) {
|
||||
_result.conductorVersion = conductorVersion;
|
||||
}
|
||||
return _result;
|
||||
}
|
||||
factory ConductorState.fromBuffer($core.List<$core.int> i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromBuffer(i, r);
|
||||
factory ConductorState.fromJson($core.String i, [$pb.ExtensionRegistry r = $pb.ExtensionRegistry.EMPTY]) =>
|
||||
create()..mergeFromJson(i, r);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.deepCopy] instead. '
|
||||
'Will be removed in next major version')
|
||||
ConductorState clone() => ConductorState()..mergeFromMessage(this);
|
||||
@$core.Deprecated('Using this can add significant overhead to your binary. '
|
||||
'Use [GeneratedMessageGenericExtensions.rebuild] instead. '
|
||||
'Will be removed in next major version')
|
||||
ConductorState copyWith(void Function(ConductorState) updates) =>
|
||||
super.copyWith((message) => updates(message as ConductorState))
|
||||
as ConductorState; // ignore: deprecated_member_use
|
||||
$pb.BuilderInfo get info_ => _i;
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ConductorState create() => ConductorState._();
|
||||
ConductorState createEmptyInstance() => create();
|
||||
static $pb.PbList<ConductorState> createRepeated() => $pb.PbList<ConductorState>();
|
||||
@$core.pragma('dart2js:noInline')
|
||||
static ConductorState getDefault() => _defaultInstance ??= $pb.GeneratedMessage.$_defaultFor<ConductorState>(create);
|
||||
static ConductorState _defaultInstance;
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.String get releaseChannel => $_getSZ(0);
|
||||
@$pb.TagNumber(1)
|
||||
set releaseChannel($core.String v) {
|
||||
$_setString(0, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(1)
|
||||
$core.bool hasReleaseChannel() => $_has(0);
|
||||
@$pb.TagNumber(1)
|
||||
void clearReleaseChannel() => clearField(1);
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.String get releaseVersion => $_getSZ(1);
|
||||
@$pb.TagNumber(2)
|
||||
set releaseVersion($core.String v) {
|
||||
$_setString(1, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(2)
|
||||
$core.bool hasReleaseVersion() => $_has(1);
|
||||
@$pb.TagNumber(2)
|
||||
void clearReleaseVersion() => clearField(2);
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
Repository get engine => $_getN(2);
|
||||
@$pb.TagNumber(4)
|
||||
set engine(Repository v) {
|
||||
setField(4, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(4)
|
||||
$core.bool hasEngine() => $_has(2);
|
||||
@$pb.TagNumber(4)
|
||||
void clearEngine() => clearField(4);
|
||||
@$pb.TagNumber(4)
|
||||
Repository ensureEngine() => $_ensure(2);
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
Repository get framework => $_getN(3);
|
||||
@$pb.TagNumber(5)
|
||||
set framework(Repository v) {
|
||||
setField(5, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(5)
|
||||
$core.bool hasFramework() => $_has(3);
|
||||
@$pb.TagNumber(5)
|
||||
void clearFramework() => clearField(5);
|
||||
@$pb.TagNumber(5)
|
||||
Repository ensureFramework() => $_ensure(3);
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$fixnum.Int64 get createdDate => $_getI64(4);
|
||||
@$pb.TagNumber(6)
|
||||
set createdDate($fixnum.Int64 v) {
|
||||
$_setInt64(4, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(6)
|
||||
$core.bool hasCreatedDate() => $_has(4);
|
||||
@$pb.TagNumber(6)
|
||||
void clearCreatedDate() => clearField(6);
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$fixnum.Int64 get lastUpdatedDate => $_getI64(5);
|
||||
@$pb.TagNumber(7)
|
||||
set lastUpdatedDate($fixnum.Int64 v) {
|
||||
$_setInt64(5, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(7)
|
||||
$core.bool hasLastUpdatedDate() => $_has(5);
|
||||
@$pb.TagNumber(7)
|
||||
void clearLastUpdatedDate() => clearField(7);
|
||||
|
||||
@$pb.TagNumber(8)
|
||||
$core.List<$core.String> get logs => $_getList(6);
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
ReleasePhase get lastPhase => $_getN(7);
|
||||
@$pb.TagNumber(9)
|
||||
set lastPhase(ReleasePhase v) {
|
||||
setField(9, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(9)
|
||||
$core.bool hasLastPhase() => $_has(7);
|
||||
@$pb.TagNumber(9)
|
||||
void clearLastPhase() => clearField(9);
|
||||
|
||||
@$pb.TagNumber(10)
|
||||
$core.String get conductorVersion => $_getSZ(8);
|
||||
@$pb.TagNumber(10)
|
||||
set conductorVersion($core.String v) {
|
||||
$_setString(8, v);
|
||||
}
|
||||
|
||||
@$pb.TagNumber(10)
|
||||
$core.bool hasConductorVersion() => $_has(8);
|
||||
@$pb.TagNumber(10)
|
||||
void clearConductorVersion() => clearField(10);
|
||||
}
|
69
dev/tools/lib/proto/conductor_state.pbenum.dart
Normal file
69
dev/tools/lib/proto/conductor_state.pbenum.dart
Normal file
|
@ -0,0 +1,69 @@
|
|||
// 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.
|
||||
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: conductor_state.proto
|
||||
//
|
||||
// @dart = 2.7
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields
|
||||
|
||||
// ignore_for_file: UNDEFINED_SHOWN_NAME
|
||||
import 'dart:core' as $core;
|
||||
import 'package:protobuf/protobuf.dart' as $pb;
|
||||
|
||||
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 =
|
||||
ReleasePhase._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'APPLY_ENGINE_CHERRYPICKS');
|
||||
static const ReleasePhase CODESIGN_ENGINE_BINARIES =
|
||||
ReleasePhase._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'CODESIGN_ENGINE_BINARIES');
|
||||
static const ReleasePhase APPLY_FRAMEWORK_CHERRYPICKS = ReleasePhase._(
|
||||
3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'APPLY_FRAMEWORK_CHERRYPICKS');
|
||||
static const ReleasePhase PUBLISH_VERSION =
|
||||
ReleasePhase._(4, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PUBLISH_VERSION');
|
||||
static const ReleasePhase PUBLISH_CHANNEL =
|
||||
ReleasePhase._(5, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PUBLISH_CHANNEL');
|
||||
static const ReleasePhase VERIFY_RELEASE =
|
||||
ReleasePhase._(6, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'VERIFY_RELEASE');
|
||||
|
||||
static const $core.List<ReleasePhase> values = <ReleasePhase>[
|
||||
INITIALIZE,
|
||||
APPLY_ENGINE_CHERRYPICKS,
|
||||
CODESIGN_ENGINE_BINARIES,
|
||||
APPLY_FRAMEWORK_CHERRYPICKS,
|
||||
PUBLISH_VERSION,
|
||||
PUBLISH_CHANNEL,
|
||||
VERIFY_RELEASE,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, ReleasePhase> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static ReleasePhase valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const ReleasePhase._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
||||
|
||||
class CherrypickState extends $pb.ProtobufEnum {
|
||||
static const CherrypickState PENDING =
|
||||
CherrypickState._(0, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PENDING');
|
||||
static const CherrypickState PENDING_WITH_CONFLICT =
|
||||
CherrypickState._(1, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'PENDING_WITH_CONFLICT');
|
||||
static const CherrypickState COMPLETED =
|
||||
CherrypickState._(2, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'COMPLETED');
|
||||
static const CherrypickState ABANDONED =
|
||||
CherrypickState._(3, const $core.bool.fromEnvironment('protobuf.omit_enum_names') ? '' : 'ABANDONED');
|
||||
|
||||
static const $core.List<CherrypickState> values = <CherrypickState>[
|
||||
PENDING,
|
||||
PENDING_WITH_CONFLICT,
|
||||
COMPLETED,
|
||||
ABANDONED,
|
||||
];
|
||||
|
||||
static final $core.Map<$core.int, CherrypickState> _byValue = $pb.ProtobufEnum.initByValue(values);
|
||||
static CherrypickState valueOf($core.int value) => _byValue[value];
|
||||
|
||||
const CherrypickState._($core.int v, $core.String n) : super(v, n);
|
||||
}
|
107
dev/tools/lib/proto/conductor_state.pbjson.dart
Normal file
107
dev/tools/lib/proto/conductor_state.pbjson.dart
Normal file
|
@ -0,0 +1,107 @@
|
|||
// 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.
|
||||
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: conductor_state.proto
|
||||
//
|
||||
// @dart = 2.7
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
|
||||
|
||||
import 'dart:core' as $core;
|
||||
import 'dart:convert' as $convert;
|
||||
import 'dart:typed_data' as $typed_data;
|
||||
|
||||
@$core.Deprecated('Use releasePhaseDescriptor instead')
|
||||
const ReleasePhase$json = const {
|
||||
'1': 'ReleasePhase',
|
||||
'2': const [
|
||||
const {'1': 'INITIALIZE', '2': 0},
|
||||
const {'1': 'APPLY_ENGINE_CHERRYPICKS', '2': 1},
|
||||
const {'1': 'CODESIGN_ENGINE_BINARIES', '2': 2},
|
||||
const {'1': 'APPLY_FRAMEWORK_CHERRYPICKS', '2': 3},
|
||||
const {'1': 'PUBLISH_VERSION', '2': 4},
|
||||
const {'1': 'PUBLISH_CHANNEL', '2': 5},
|
||||
const {'1': 'VERIFY_RELEASE', '2': 6},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ReleasePhase`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List releasePhaseDescriptor = $convert.base64Decode(
|
||||
'CgxSZWxlYXNlUGhhc2USDgoKSU5JVElBTElaRRAAEhwKGEFQUExZX0VOR0lORV9DSEVSUllQSUNLUxABEhwKGENPREVTSUdOX0VOR0lORV9CSU5BUklFUxACEh8KG0FQUExZX0ZSQU1FV09SS19DSEVSUllQSUNLUxADEhMKD1BVQkxJU0hfVkVSU0lPThAEEhMKD1BVQkxJU0hfQ0hBTk5FTBAFEhIKDlZFUklGWV9SRUxFQVNFEAY=');
|
||||
@$core.Deprecated('Use cherrypickStateDescriptor instead')
|
||||
const CherrypickState$json = const {
|
||||
'1': 'CherrypickState',
|
||||
'2': const [
|
||||
const {'1': 'PENDING', '2': 0},
|
||||
const {'1': 'PENDING_WITH_CONFLICT', '2': 1},
|
||||
const {'1': 'COMPLETED', '2': 2},
|
||||
const {'1': 'ABANDONED', '2': 3},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `CherrypickState`. Decode as a `google.protobuf.EnumDescriptorProto`.
|
||||
final $typed_data.Uint8List cherrypickStateDescriptor = $convert.base64Decode(
|
||||
'Cg9DaGVycnlwaWNrU3RhdGUSCwoHUEVORElORxAAEhkKFVBFTkRJTkdfV0lUSF9DT05GTElDVBABEg0KCUNPTVBMRVRFRBACEg0KCUFCQU5ET05FRBAD');
|
||||
@$core.Deprecated('Use remoteDescriptor instead')
|
||||
const Remote$json = const {
|
||||
'1': 'Remote',
|
||||
'2': const [
|
||||
const {'1': 'name', '3': 1, '4': 1, '5': 9, '10': 'name'},
|
||||
const {'1': 'url', '3': 2, '4': 1, '5': 9, '10': 'url'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Remote`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List remoteDescriptor =
|
||||
$convert.base64Decode('CgZSZW1vdGUSEgoEbmFtZRgBIAEoCVIEbmFtZRIQCgN1cmwYAiABKAlSA3VybA==');
|
||||
@$core.Deprecated('Use cherrypickDescriptor instead')
|
||||
const Cherrypick$json = const {
|
||||
'1': 'Cherrypick',
|
||||
'2': const [
|
||||
const {'1': 'trunkRevision', '3': 1, '4': 1, '5': 9, '10': 'trunkRevision'},
|
||||
const {'1': 'appliedRevision', '3': 2, '4': 1, '5': 9, '10': 'appliedRevision'},
|
||||
const {'1': 'state', '3': 3, '4': 1, '5': 14, '6': '.conductor_state.CherrypickState', '10': 'state'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Cherrypick`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List cherrypickDescriptor = $convert.base64Decode(
|
||||
'CgpDaGVycnlwaWNrEiQKDXRydW5rUmV2aXNpb24YASABKAlSDXRydW5rUmV2aXNpb24SKAoPYXBwbGllZFJldmlzaW9uGAIgASgJUg9hcHBsaWVkUmV2aXNpb24SNgoFc3RhdGUYAyABKA4yIC5jb25kdWN0b3Jfc3RhdGUuQ2hlcnJ5cGlja1N0YXRlUgVzdGF0ZQ==');
|
||||
@$core.Deprecated('Use repositoryDescriptor instead')
|
||||
const Repository$json = const {
|
||||
'1': 'Repository',
|
||||
'2': const [
|
||||
const {'1': 'candidateBranch', '3': 1, '4': 1, '5': 9, '10': 'candidateBranch'},
|
||||
const {'1': 'startingGitHead', '3': 2, '4': 1, '5': 9, '10': 'startingGitHead'},
|
||||
const {'1': 'currentGitHead', '3': 3, '4': 1, '5': 9, '10': 'currentGitHead'},
|
||||
const {'1': 'checkoutPath', '3': 4, '4': 1, '5': 9, '10': 'checkoutPath'},
|
||||
const {'1': 'upstream', '3': 5, '4': 1, '5': 11, '6': '.conductor_state.Remote', '10': 'upstream'},
|
||||
const {'1': 'mirror', '3': 6, '4': 1, '5': 11, '6': '.conductor_state.Remote', '10': 'mirror'},
|
||||
const {'1': 'cherrypicks', '3': 7, '4': 3, '5': 11, '6': '.conductor_state.Cherrypick', '10': 'cherrypicks'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `Repository`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List repositoryDescriptor = $convert.base64Decode(
|
||||
'CgpSZXBvc2l0b3J5EigKD2NhbmRpZGF0ZUJyYW5jaBgBIAEoCVIPY2FuZGlkYXRlQnJhbmNoEigKD3N0YXJ0aW5nR2l0SGVhZBgCIAEoCVIPc3RhcnRpbmdHaXRIZWFkEiYKDmN1cnJlbnRHaXRIZWFkGAMgASgJUg5jdXJyZW50R2l0SGVhZBIiCgxjaGVja291dFBhdGgYBCABKAlSDGNoZWNrb3V0UGF0aBIzCgh1cHN0cmVhbRgFIAEoCzIXLmNvbmR1Y3Rvcl9zdGF0ZS5SZW1vdGVSCHVwc3RyZWFtEi8KBm1pcnJvchgGIAEoCzIXLmNvbmR1Y3Rvcl9zdGF0ZS5SZW1vdGVSBm1pcnJvchI9CgtjaGVycnlwaWNrcxgHIAMoCzIbLmNvbmR1Y3Rvcl9zdGF0ZS5DaGVycnlwaWNrUgtjaGVycnlwaWNrcw==');
|
||||
@$core.Deprecated('Use conductorStateDescriptor instead')
|
||||
const ConductorState$json = const {
|
||||
'1': 'ConductorState',
|
||||
'2': const [
|
||||
const {'1': 'releaseChannel', '3': 1, '4': 1, '5': 9, '10': 'releaseChannel'},
|
||||
const {'1': 'releaseVersion', '3': 2, '4': 1, '5': 9, '10': 'releaseVersion'},
|
||||
const {'1': 'engine', '3': 4, '4': 1, '5': 11, '6': '.conductor_state.Repository', '10': 'engine'},
|
||||
const {'1': 'framework', '3': 5, '4': 1, '5': 11, '6': '.conductor_state.Repository', '10': 'framework'},
|
||||
const {'1': 'createdDate', '3': 6, '4': 1, '5': 3, '10': 'createdDate'},
|
||||
const {'1': 'lastUpdatedDate', '3': 7, '4': 1, '5': 3, '10': 'lastUpdatedDate'},
|
||||
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': 'conductor_version', '3': 10, '4': 1, '5': 9, '10': 'conductorVersion'},
|
||||
],
|
||||
};
|
||||
|
||||
/// Descriptor for `ConductorState`. Decode as a `google.protobuf.DescriptorProto`.
|
||||
final $typed_data.Uint8List conductorStateDescriptor = $convert.base64Decode(
|
||||
'Cg5Db25kdWN0b3JTdGF0ZRImCg5yZWxlYXNlQ2hhbm5lbBgBIAEoCVIOcmVsZWFzZUNoYW5uZWwSJgoOcmVsZWFzZVZlcnNpb24YAiABKAlSDnJlbGVhc2VWZXJzaW9uEjMKBmVuZ2luZRgEIAEoCzIbLmNvbmR1Y3Rvcl9zdGF0ZS5SZXBvc2l0b3J5UgZlbmdpbmUSOQoJZnJhbWV3b3JrGAUgASgLMhsuY29uZHVjdG9yX3N0YXRlLlJlcG9zaXRvcnlSCWZyYW1ld29yaxIgCgtjcmVhdGVkRGF0ZRgGIAEoA1ILY3JlYXRlZERhdGUSKAoPbGFzdFVwZGF0ZWREYXRlGAcgASgDUg9sYXN0VXBkYXRlZERhdGUSEgoEbG9ncxgIIAMoCVIEbG9ncxI7CglsYXN0UGhhc2UYCSABKA4yHS5jb25kdWN0b3Jfc3RhdGUuUmVsZWFzZVBoYXNlUglsYXN0UGhhc2USKwoRY29uZHVjdG9yX3ZlcnNpb24YCiABKAlSEGNvbmR1Y3RvclZlcnNpb24=');
|
12
dev/tools/lib/proto/conductor_state.pbserver.dart
Normal file
12
dev/tools/lib/proto/conductor_state.pbserver.dart
Normal file
|
@ -0,0 +1,12 @@
|
|||
// 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.
|
||||
|
||||
///
|
||||
// Generated code. Do not modify.
|
||||
// source: conductor_state.proto
|
||||
//
|
||||
// @dart = 2.7
|
||||
// ignore_for_file: annotate_overrides,camel_case_types,unnecessary_const,non_constant_identifier_names,library_prefixes,unused_import,unused_shown_name,return_of_invalid_type,unnecessary_this,prefer_final_fields,deprecated_member_use_from_same_package
|
||||
|
||||
export 'conductor_state.pb.dart';
|
103
dev/tools/lib/proto/conductor_state.proto
Normal file
103
dev/tools/lib/proto/conductor_state.proto
Normal file
|
@ -0,0 +1,103 @@
|
|||
syntax = "proto3";
|
||||
|
||||
package conductor_state;
|
||||
|
||||
// A git remote
|
||||
message Remote {
|
||||
string name = 1;
|
||||
string url = 2;
|
||||
}
|
||||
|
||||
enum ReleasePhase {
|
||||
// Release was started with `conductor start` and repositories cloned.
|
||||
INITIALIZE = 0;
|
||||
APPLY_ENGINE_CHERRYPICKS = 1;
|
||||
CODESIGN_ENGINE_BINARIES = 2;
|
||||
APPLY_FRAMEWORK_CHERRYPICKS = 3;
|
||||
|
||||
// Git tag applied to framework RC branch HEAD and pushed upstream.
|
||||
PUBLISH_VERSION = 4;
|
||||
|
||||
// RC branch HEAD pushed to upstream release branch.
|
||||
//
|
||||
// For example, flutter-1.2-candidate.3 -> upstream/beta
|
||||
PUBLISH_CHANNEL = 5;
|
||||
|
||||
// Package artifacts verified to exist on cloud storage.
|
||||
VERIFY_RELEASE = 6;
|
||||
}
|
||||
|
||||
enum CherrypickState {
|
||||
// The cherrypick has not yet been applied.
|
||||
PENDING = 0;
|
||||
|
||||
// The cherrypick has not been applied and will require manual resolution.
|
||||
PENDING_WITH_CONFLICT = 1;
|
||||
|
||||
// The cherrypick has been successfully applied to the local checkout.
|
||||
//
|
||||
// This state requires Cherrypick.appliedRevision to also be set.
|
||||
COMPLETED = 2;
|
||||
|
||||
// The cherrypick will NOT be applied in this release.
|
||||
ABANDONED = 3;
|
||||
}
|
||||
|
||||
message Cherrypick {
|
||||
// The revision on trunk to cherrypick.
|
||||
string trunkRevision = 1;
|
||||
|
||||
// Once applied, the actual commit revision of the cherrypick.
|
||||
string appliedRevision = 2;
|
||||
|
||||
CherrypickState state = 3;
|
||||
}
|
||||
|
||||
message Repository {
|
||||
// The development git branch the release is based on.
|
||||
//
|
||||
// Must be of the form /flutter-(\d+)\.(\d+)-candidate\.(\d+)/
|
||||
string candidateBranch = 1;
|
||||
|
||||
// The commit hash at the tip of the branch before cherrypicks were applied.
|
||||
string startingGitHead = 2;
|
||||
|
||||
// The difference in commits between this and [startingGitHead] is the number
|
||||
// of cherrypicks that have been currently applied.
|
||||
string currentGitHead = 3;
|
||||
|
||||
// Path to the git checkout on local disk.
|
||||
string checkoutPath = 4;
|
||||
|
||||
// The remote commits will be fetched from.
|
||||
Remote upstream = 5;
|
||||
|
||||
// The remote cherrypicks will be pushed to to create a Pull Request.
|
||||
//
|
||||
// This should be a mirror owned by the user conducting the release.
|
||||
Remote mirror = 6;
|
||||
|
||||
// Desired cherrypicks.
|
||||
repeated Cherrypick cherrypicks = 7;
|
||||
}
|
||||
|
||||
message ConductorState {
|
||||
// One of 'stable', 'beta', or 'dev'
|
||||
string releaseChannel = 1;
|
||||
|
||||
// The name of the release.
|
||||
string releaseVersion = 2;
|
||||
|
||||
Repository engine = 4;
|
||||
Repository framework = 5;
|
||||
int64 createdDate = 6;
|
||||
int64 lastUpdatedDate = 7;
|
||||
|
||||
repeated string logs = 8;
|
||||
|
||||
// The last [ReleasePhase] that was successfully completed.
|
||||
ReleasePhase lastPhase = 9;
|
||||
|
||||
// Commit hash of the Conductor tool.
|
||||
string conductor_version = 10;
|
||||
}
|
3
dev/tools/lib/proto/license_header.txt
Normal file
3
dev/tools/lib/proto/license_header.txt
Normal file
|
@ -0,0 +1,3 @@
|
|||
// 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.
|
|
@ -11,28 +11,68 @@ import 'package:process/process.dart';
|
|||
import 'package:platform/platform.dart';
|
||||
|
||||
import './git.dart';
|
||||
import './globals.dart' as globals;
|
||||
import './globals.dart';
|
||||
import './stdio.dart';
|
||||
import './version.dart';
|
||||
|
||||
/// Allowed git remote names.
|
||||
enum RemoteName {
|
||||
upstream,
|
||||
mirror,
|
||||
}
|
||||
|
||||
class Remote {
|
||||
const Remote({
|
||||
@required RemoteName name,
|
||||
@required this.url,
|
||||
}) : _name = name;
|
||||
|
||||
final RemoteName _name;
|
||||
|
||||
/// The name of the remote.
|
||||
String get name {
|
||||
switch (_name) {
|
||||
case RemoteName.upstream:
|
||||
return 'upstream';
|
||||
case RemoteName.mirror:
|
||||
return 'mirror';
|
||||
}
|
||||
throw ConductorException('Invalid value of _name: $_name'); // For analyzer
|
||||
}
|
||||
|
||||
/// The URL of the remote.
|
||||
final String url;
|
||||
}
|
||||
|
||||
/// A source code repository.
|
||||
abstract class Repository {
|
||||
Repository({
|
||||
@required this.name,
|
||||
@required this.upstream,
|
||||
@required this.fetchRemote,
|
||||
@required this.processManager,
|
||||
@required this.stdio,
|
||||
@required this.platform,
|
||||
@required this.fileSystem,
|
||||
@required this.parentDirectory,
|
||||
this.initialRef,
|
||||
this.localUpstream = false,
|
||||
this.useExistingCheckout = false,
|
||||
this.pushRemote,
|
||||
}) : git = Git(processManager),
|
||||
assert(localUpstream != null),
|
||||
assert(useExistingCheckout != null);
|
||||
|
||||
final String name;
|
||||
final String upstream;
|
||||
final Remote fetchRemote;
|
||||
|
||||
/// Remote to publish tags and commits to.
|
||||
///
|
||||
/// This value can be null, in which case attempting to publish will lead to
|
||||
/// a [ConductorException].
|
||||
final Remote pushRemote;
|
||||
|
||||
/// The initial ref (branch or commit name) to check out.
|
||||
final String initialRef;
|
||||
final Git git;
|
||||
final ProcessManager processManager;
|
||||
final Stdio stdio;
|
||||
|
@ -55,33 +95,49 @@ abstract class Repository {
|
|||
return _checkoutDirectory;
|
||||
}
|
||||
_checkoutDirectory = parentDirectory.childDirectory(name);
|
||||
lazilyInitialize();
|
||||
return _checkoutDirectory;
|
||||
}
|
||||
|
||||
/// Ensure the repository is cloned to disk and initialized with proper state.
|
||||
void lazilyInitialize() {
|
||||
if (!useExistingCheckout && _checkoutDirectory.existsSync()) {
|
||||
stdio.printTrace('Deleting $name from ${_checkoutDirectory.path}...');
|
||||
_checkoutDirectory.deleteSync(recursive: true);
|
||||
} else if (useExistingCheckout && _checkoutDirectory.existsSync()) {
|
||||
git.run(
|
||||
<String>['checkout', 'master'],
|
||||
'Checkout to master branch',
|
||||
workingDirectory: _checkoutDirectory.path,
|
||||
);
|
||||
git.run(
|
||||
<String>['pull', '--ff-only'],
|
||||
'Updating $name repo',
|
||||
workingDirectory: _checkoutDirectory.path,
|
||||
);
|
||||
}
|
||||
|
||||
if (!_checkoutDirectory.existsSync()) {
|
||||
stdio.printTrace(
|
||||
'Cloning $name from $upstream to ${_checkoutDirectory.path}...');
|
||||
'Cloning $name from ${fetchRemote.url} to ${_checkoutDirectory.path}...',
|
||||
);
|
||||
git.run(
|
||||
<String>['clone', '--', upstream, _checkoutDirectory.path],
|
||||
<String>[
|
||||
'clone',
|
||||
'--origin',
|
||||
fetchRemote.name,
|
||||
'--',
|
||||
fetchRemote.url,
|
||||
_checkoutDirectory.path
|
||||
],
|
||||
'Cloning $name repo',
|
||||
workingDirectory: parentDirectory.path,
|
||||
);
|
||||
if (pushRemote != null) {
|
||||
git.run(
|
||||
<String>['remote', 'add', pushRemote.name, pushRemote.url],
|
||||
'Adding remote ${pushRemote.url} as ${pushRemote.name}',
|
||||
workingDirectory: _checkoutDirectory.path,
|
||||
);
|
||||
git.run(
|
||||
<String>['fetch', pushRemote.name],
|
||||
'Fetching git remote ${pushRemote.name}',
|
||||
workingDirectory: _checkoutDirectory.path,
|
||||
);
|
||||
}
|
||||
if (localUpstream) {
|
||||
// These branches must exist locally for the repo that depends on it
|
||||
// to fetch and push to.
|
||||
for (final String channel in globals.kReleaseChannels) {
|
||||
for (final String channel in kReleaseChannels) {
|
||||
git.run(
|
||||
<String>['checkout', channel, '--'],
|
||||
'check out branch $channel locally',
|
||||
|
@ -91,10 +147,17 @@ abstract class Repository {
|
|||
}
|
||||
}
|
||||
|
||||
if (initialRef != null) {
|
||||
git.run(
|
||||
<String>['checkout', '${fetchRemote.name}/$initialRef'],
|
||||
'Checking out initialRef $initialRef',
|
||||
workingDirectory: _checkoutDirectory.path,
|
||||
);
|
||||
}
|
||||
final String revision = reverseParse('HEAD');
|
||||
stdio
|
||||
.printTrace('Repository $name is checked out at revision "$revision".');
|
||||
return _checkoutDirectory;
|
||||
stdio.printTrace(
|
||||
'Repository $name is checked out at revision "$revision".',
|
||||
);
|
||||
}
|
||||
|
||||
/// The URL of the remote named [remoteName].
|
||||
|
@ -117,6 +180,15 @@ abstract class Repository {
|
|||
return output == '';
|
||||
}
|
||||
|
||||
/// Return the revision for the branch point between two refs.
|
||||
String branchPoint(String firstRef, String secondRef) {
|
||||
return git.getOutput(
|
||||
<String>['merge-base', firstRef, secondRef],
|
||||
'determine the merge base between $firstRef and $secondRef',
|
||||
workingDirectory: checkoutDirectory.path,
|
||||
).trim();
|
||||
}
|
||||
|
||||
/// Fetch all branches and associated commits and tags from [remoteName].
|
||||
void fetch(String remoteName) {
|
||||
git.run(
|
||||
|
@ -126,10 +198,22 @@ abstract class Repository {
|
|||
);
|
||||
}
|
||||
|
||||
void checkout(String revision) {
|
||||
/// Create (and checkout) a new branch based on the current HEAD.
|
||||
///
|
||||
/// Runs `git checkout -b $branchName`.
|
||||
void newBranch(String branchName) {
|
||||
git.run(
|
||||
<String>['checkout', revision],
|
||||
'checkout $revision',
|
||||
<String>['checkout', '-b', branchName],
|
||||
'create & checkout new branch $branchName',
|
||||
workingDirectory: checkoutDirectory.path,
|
||||
);
|
||||
}
|
||||
|
||||
/// Check out the given ref.
|
||||
void checkout(String ref) {
|
||||
git.run(
|
||||
<String>['checkout', ref],
|
||||
'checkout ref',
|
||||
workingDirectory: checkoutDirectory.path,
|
||||
);
|
||||
}
|
||||
|
@ -146,13 +230,25 @@ abstract class Repository {
|
|||
);
|
||||
}
|
||||
|
||||
/// List commits in reverse chronological order.
|
||||
List<String> revList(List<String> args) {
|
||||
return git
|
||||
.getOutput(
|
||||
<String>['rev-list', ...args],
|
||||
'rev-list with args ${args.join(' ')}',
|
||||
workingDirectory: checkoutDirectory.path,
|
||||
)
|
||||
.trim()
|
||||
.split('\n');
|
||||
}
|
||||
|
||||
/// Look up the commit for [ref].
|
||||
String reverseParse(String ref) {
|
||||
final String revisionHash = git.getOutput(
|
||||
<String>['rev-parse', ref],
|
||||
'look up the commit for the ref $ref',
|
||||
workingDirectory: checkoutDirectory.path,
|
||||
).trim();
|
||||
);
|
||||
assert(revisionHash.isNotEmpty);
|
||||
return revisionHash;
|
||||
}
|
||||
|
@ -184,11 +280,55 @@ abstract class Repository {
|
|||
return exitcode == 0;
|
||||
}
|
||||
|
||||
/// Resets repository HEAD to [commit].
|
||||
void reset(String commit) {
|
||||
/// Determines if a commit will cherry-pick to current HEAD without conflict.
|
||||
bool canCherryPick(String commit) {
|
||||
assert(
|
||||
gitCheckoutClean(),
|
||||
'cannot cherry-pick because git checkout ${checkoutDirectory.path} is not clean',
|
||||
);
|
||||
|
||||
final int exitcode = git.run(
|
||||
<String>['cherry-pick', '--no-commit', commit],
|
||||
'attempt to cherry-pick $commit without committing',
|
||||
allowNonZeroExitCode: true,
|
||||
workingDirectory: checkoutDirectory.path,
|
||||
);
|
||||
|
||||
final bool result = exitcode == 0;
|
||||
|
||||
if (result == false) {
|
||||
stdio.printError(git.getOutput(
|
||||
<String>['diff'],
|
||||
'get diff of failed cherry-pick',
|
||||
workingDirectory: checkoutDirectory.path,
|
||||
));
|
||||
}
|
||||
|
||||
reset('HEAD');
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Cherry-pick a [commit] to the current HEAD.
|
||||
///
|
||||
/// This method will throw a [GitException] if the command fails.
|
||||
void cherryPick(String commit) {
|
||||
assert(
|
||||
gitCheckoutClean(),
|
||||
'cannot cherry-pick because git checkout ${checkoutDirectory.path} is not clean',
|
||||
);
|
||||
|
||||
git.run(
|
||||
<String>['reset', commit, '--hard'],
|
||||
'reset to the release commit',
|
||||
<String>['cherry-pick', '--no-commit', commit],
|
||||
'attempt to cherry-pick $commit without committing',
|
||||
workingDirectory: checkoutDirectory.path,
|
||||
);
|
||||
}
|
||||
|
||||
/// Resets repository HEAD to [ref].
|
||||
void reset(String ref) {
|
||||
git.run(
|
||||
<String>['reset', ref, '--hard'],
|
||||
'reset to $ref',
|
||||
workingDirectory: checkoutDirectory.path,
|
||||
);
|
||||
}
|
||||
|
@ -260,12 +400,17 @@ class FrameworkRepository extends Repository {
|
|||
FrameworkRepository(
|
||||
this.checkouts, {
|
||||
String name = 'framework',
|
||||
String upstream = FrameworkRepository.defaultUpstream,
|
||||
Remote fetchRemote = const Remote(
|
||||
name: RemoteName.upstream, url: FrameworkRepository.defaultUpstream),
|
||||
bool localUpstream = false,
|
||||
bool useExistingCheckout = false,
|
||||
String initialRef,
|
||||
Remote pushRemote,
|
||||
}) : super(
|
||||
name: name,
|
||||
upstream: upstream,
|
||||
fetchRemote: fetchRemote,
|
||||
pushRemote: pushRemote,
|
||||
initialRef: initialRef,
|
||||
fileSystem: checkouts.fileSystem,
|
||||
localUpstream: localUpstream,
|
||||
parentDirectory: checkouts.directory,
|
||||
|
@ -288,7 +433,10 @@ class FrameworkRepository extends Repository {
|
|||
return FrameworkRepository(
|
||||
checkouts,
|
||||
name: name,
|
||||
upstream: 'file://$upstreamPath/',
|
||||
fetchRemote: Remote(
|
||||
name: RemoteName.upstream,
|
||||
url: 'file://$upstreamPath/',
|
||||
),
|
||||
localUpstream: false,
|
||||
useExistingCheckout: useExistingCheckout,
|
||||
);
|
||||
|
@ -298,6 +446,8 @@ class FrameworkRepository extends Repository {
|
|||
static const String defaultUpstream =
|
||||
'https://github.com/flutter/flutter.git';
|
||||
|
||||
static const String defaultBranch = 'master';
|
||||
|
||||
String get cacheDirectory => fileSystem.path.join(
|
||||
checkoutDirectory.path,
|
||||
'bin',
|
||||
|
@ -311,7 +461,8 @@ class FrameworkRepository extends Repository {
|
|||
return FrameworkRepository(
|
||||
checkouts,
|
||||
name: cloneName,
|
||||
upstream: 'file://${checkoutDirectory.path}/',
|
||||
fetchRemote: Remote(
|
||||
name: RemoteName.upstream, url: 'file://${checkoutDirectory.path}/'),
|
||||
useExistingCheckout: useExistingCheckout,
|
||||
);
|
||||
}
|
||||
|
@ -345,8 +496,8 @@ class FrameworkRepository extends Repository {
|
|||
}
|
||||
|
||||
@override
|
||||
void checkout(String revision) {
|
||||
super.checkout(revision);
|
||||
void checkout(String ref) {
|
||||
super.checkout(ref);
|
||||
// The tool will overwrite old cached artifacts, but not delete unused
|
||||
// artifacts from a previous version. Thus, delete the entire cache and
|
||||
// re-populate.
|
||||
|
@ -363,12 +514,55 @@ class FrameworkRepository extends Repository {
|
|||
final io.ProcessResult result =
|
||||
runFlutter(<String>['--version', '--machine']);
|
||||
final Map<String, dynamic> versionJson = jsonDecode(
|
||||
globals.stdoutToString(result.stdout),
|
||||
stdoutToString(result.stdout),
|
||||
) as Map<String, dynamic>;
|
||||
return Version.fromString(versionJson['frameworkVersion'] as String);
|
||||
}
|
||||
}
|
||||
|
||||
class EngineRepository extends Repository {
|
||||
EngineRepository(
|
||||
this.checkouts, {
|
||||
String name = 'engine',
|
||||
String initialRef = EngineRepository.defaultBranch,
|
||||
Remote fetchRemote = const Remote(
|
||||
name: RemoteName.upstream, url: EngineRepository.defaultUpstream),
|
||||
bool localUpstream = false,
|
||||
bool useExistingCheckout = false,
|
||||
Remote pushRemote,
|
||||
}) : super(
|
||||
name: name,
|
||||
fetchRemote: fetchRemote,
|
||||
pushRemote: pushRemote,
|
||||
initialRef: initialRef,
|
||||
fileSystem: checkouts.fileSystem,
|
||||
localUpstream: localUpstream,
|
||||
parentDirectory: checkouts.directory,
|
||||
platform: checkouts.platform,
|
||||
processManager: checkouts.processManager,
|
||||
stdio: checkouts.stdio,
|
||||
useExistingCheckout: useExistingCheckout,
|
||||
);
|
||||
|
||||
final Checkouts checkouts;
|
||||
|
||||
static const String defaultUpstream = 'https://github.com/flutter/engine.git';
|
||||
static const String defaultBranch = 'master';
|
||||
|
||||
@override
|
||||
Repository cloneRepository(String cloneName) {
|
||||
assert(localUpstream);
|
||||
cloneName ??= 'clone-of-$name';
|
||||
return EngineRepository(
|
||||
checkouts,
|
||||
name: cloneName,
|
||||
fetchRemote: Remote(
|
||||
name: RemoteName.upstream, url: 'file://${checkoutDirectory.path}/'),
|
||||
useExistingCheckout: useExistingCheckout,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// An enum of all the repositories that the Conductor supports.
|
||||
enum RepositoryType {
|
||||
framework,
|
||||
|
|
|
@ -8,11 +8,18 @@ import 'package:file/file.dart';
|
|||
import 'package:meta/meta.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import './globals.dart';
|
||||
import './repository.dart';
|
||||
import './stdio.dart';
|
||||
import './version.dart';
|
||||
|
||||
const String kIncrement = 'increment';
|
||||
const String kCommit = 'commit';
|
||||
const String kRemoteName = 'remote';
|
||||
const String kJustPrint = 'just-print';
|
||||
const String kYes = 'yes';
|
||||
const String kForce = 'force';
|
||||
const String kSkipTagging = 'skip-tagging';
|
||||
|
||||
/// Create a new dev release without cherry picks.
|
||||
class RollDevCommand extends Command<void> {
|
||||
RollDevCommand({
|
||||
|
@ -57,7 +64,17 @@ class RollDevCommand extends Command<void> {
|
|||
help: 'Do not create tag and push to remote, only update release branch. '
|
||||
'For recovering when the script fails trying to git push to the release branch.'
|
||||
);
|
||||
argParser.addFlag(kYes, negatable: false, abbr: 'y', help: 'Skip the confirmation prompt.');
|
||||
argParser.addFlag(
|
||||
kYes,
|
||||
negatable: false,
|
||||
abbr: 'y',
|
||||
help: 'Skip the confirmation prompt.',
|
||||
);
|
||||
argParser.addOption(
|
||||
kRemoteName,
|
||||
help: 'Specifies which git remote to fetch from.',
|
||||
defaultsTo: 'upstream',
|
||||
);
|
||||
}
|
||||
|
||||
final Checkouts checkouts;
|
||||
|
@ -92,8 +109,8 @@ bool rollDev({
|
|||
@required ArgResults argResults,
|
||||
@required Stdio stdio,
|
||||
@required FrameworkRepository repository,
|
||||
String remoteName = 'origin',
|
||||
}) {
|
||||
final String remoteName = argResults[kRemoteName] as String;
|
||||
final String level = argResults[kIncrement] as String;
|
||||
final String commit = argResults[kCommit] as String;
|
||||
final bool justPrint = argResults[kJustPrint] as bool;
|
||||
|
|
348
dev/tools/lib/start.dart
Normal file
348
dev/tools/lib/start.dart
Normal file
|
@ -0,0 +1,348 @@
|
|||
// 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 'dart:convert' show jsonEncode;
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:fixnum/fixnum.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import './git.dart';
|
||||
import './globals.dart';
|
||||
import './proto/conductor_state.pb.dart' as pb;
|
||||
import './proto/conductor_state.pbenum.dart' show ReleasePhase;
|
||||
import './repository.dart';
|
||||
import './state.dart';
|
||||
import './stdio.dart';
|
||||
|
||||
const String kCandidateOption = 'candidate-branch';
|
||||
const String kReleaseOption = 'release-channel';
|
||||
const String kStateOption = 'state-file';
|
||||
const String kFrameworkMirrorOption = 'framework-mirror';
|
||||
const String kEngineMirrorOption = 'engine-mirror';
|
||||
const String kFrameworkUpstreamOption = 'framework-upstream';
|
||||
const String kEngineUpstreamOption = 'engine-upstream';
|
||||
const String kFrameworkCherrypicksOption = 'framework-cherrypicks';
|
||||
const String kEngineCherrypicksOption = 'engine-cherrypicks';
|
||||
|
||||
/// Command to print the status of the current Flutter release.
|
||||
class StartCommand extends Command<void> {
|
||||
StartCommand({
|
||||
@required this.checkouts,
|
||||
@required this.flutterRoot,
|
||||
}) : platform = checkouts.platform,
|
||||
processManager = checkouts.processManager,
|
||||
fileSystem = checkouts.fileSystem,
|
||||
stdio = checkouts.stdio {
|
||||
final String defaultPath = defaultStateFilePath(platform);
|
||||
argParser.addOption(
|
||||
kCandidateOption,
|
||||
help: 'The candidate branch the release will be based on.',
|
||||
);
|
||||
argParser.addOption(
|
||||
kReleaseOption,
|
||||
help: 'The target release channel for the release.',
|
||||
allowed: <String>['stable', 'beta', 'dev'],
|
||||
);
|
||||
argParser.addOption(
|
||||
kFrameworkUpstreamOption,
|
||||
defaultsTo: FrameworkRepository.defaultUpstream,
|
||||
help:
|
||||
'Configurable Framework repo upstream remote. Primarily for testing.',
|
||||
hide: true,
|
||||
);
|
||||
argParser.addOption(
|
||||
kEngineUpstreamOption,
|
||||
defaultsTo: EngineRepository.defaultUpstream,
|
||||
help: 'Configurable Engine repo upstream remote. Primarily for testing.',
|
||||
hide: true,
|
||||
);
|
||||
argParser.addOption(
|
||||
kFrameworkMirrorOption,
|
||||
help: 'Framework repo mirror remote.',
|
||||
);
|
||||
argParser.addOption(
|
||||
kEngineMirrorOption,
|
||||
help: 'Engine repo mirror remote.',
|
||||
);
|
||||
argParser.addOption(
|
||||
kStateOption,
|
||||
defaultsTo: defaultPath,
|
||||
help: 'Path to persistent state file. Defaults to $defaultPath',
|
||||
);
|
||||
argParser.addMultiOption(
|
||||
kEngineCherrypicksOption,
|
||||
help: 'Engine cherrypick hashes to be applied.',
|
||||
defaultsTo: <String>[],
|
||||
);
|
||||
argParser.addMultiOption(
|
||||
kFrameworkCherrypicksOption,
|
||||
help: 'Framework cherrypick hashes to be applied.',
|
||||
defaultsTo: <String>[],
|
||||
);
|
||||
final Git git = Git(processManager);
|
||||
conductorVersion = git.getOutput(
|
||||
<String>['rev-parse', 'HEAD'],
|
||||
'look up the current revision.',
|
||||
workingDirectory: flutterRoot.path,
|
||||
).trim();
|
||||
|
||||
assert(conductorVersion.isNotEmpty);
|
||||
}
|
||||
|
||||
final Checkouts checkouts;
|
||||
|
||||
/// The root directory of the Flutter repository that houses the Conductor.
|
||||
///
|
||||
/// This directory is used to check the git revision of the Conductor.
|
||||
final Directory flutterRoot;
|
||||
final FileSystem fileSystem;
|
||||
final Platform platform;
|
||||
final ProcessManager processManager;
|
||||
final Stdio stdio;
|
||||
|
||||
/// Git revision for the currently running Conductor.
|
||||
String conductorVersion;
|
||||
|
||||
@override
|
||||
String get name => 'start';
|
||||
|
||||
@override
|
||||
String get description => 'Initialize a new Flutter release.';
|
||||
|
||||
@override
|
||||
void run() {
|
||||
if (!platform.isMacOS && !platform.isLinux) {
|
||||
throw ConductorException(
|
||||
'Error! This tool is only supported on macOS and Linux',
|
||||
);
|
||||
}
|
||||
|
||||
final File stateFile = checkouts.fileSystem.file(
|
||||
getValueFromEnvOrArgs(kStateOption, argResults, platform.environment),
|
||||
);
|
||||
if (stateFile.existsSync()) {
|
||||
throw ConductorException(
|
||||
'Error! A persistent state file already found at ${argResults[kStateOption]}.\n\n'
|
||||
'Run `conductor clean` to cancel a previous release.');
|
||||
}
|
||||
final String frameworkUpstream = getValueFromEnvOrArgs(
|
||||
kFrameworkUpstreamOption,
|
||||
argResults,
|
||||
platform.environment,
|
||||
);
|
||||
final String frameworkMirror = getValueFromEnvOrArgs(
|
||||
kFrameworkMirrorOption,
|
||||
argResults,
|
||||
platform.environment,
|
||||
);
|
||||
final String engineUpstream = getValueFromEnvOrArgs(
|
||||
kEngineUpstreamOption,
|
||||
argResults,
|
||||
platform.environment,
|
||||
);
|
||||
final String engineMirror = getValueFromEnvOrArgs(
|
||||
kEngineMirrorOption,
|
||||
argResults,
|
||||
platform.environment,
|
||||
);
|
||||
final String candidateBranch = getValueFromEnvOrArgs(
|
||||
kCandidateOption,
|
||||
argResults,
|
||||
platform.environment,
|
||||
);
|
||||
final String releaseChannel = getValueFromEnvOrArgs(
|
||||
kReleaseOption,
|
||||
argResults,
|
||||
platform.environment,
|
||||
);
|
||||
final List<String> frameworkCherrypickRevisions = getValuesFromEnvOrArgs(
|
||||
kFrameworkCherrypicksOption,
|
||||
argResults,
|
||||
platform.environment,
|
||||
);
|
||||
final List<String> engineCherrypickRevisions = getValuesFromEnvOrArgs(
|
||||
kEngineCherrypicksOption,
|
||||
argResults,
|
||||
platform.environment,
|
||||
);
|
||||
if (!releaseCandidateBranchRegex.hasMatch(candidateBranch)) {
|
||||
throw ConductorException(
|
||||
'Invalid release candidate branch "$candidateBranch". '
|
||||
'Text should match the regex pattern /${releaseCandidateBranchRegex.pattern}/.');
|
||||
}
|
||||
|
||||
final Int64 unixDate = Int64(DateTime.now().millisecondsSinceEpoch);
|
||||
final pb.ConductorState state = pb.ConductorState();
|
||||
|
||||
state.releaseChannel = releaseChannel;
|
||||
state.createdDate = unixDate;
|
||||
state.lastUpdatedDate = unixDate;
|
||||
|
||||
final EngineRepository engine = EngineRepository(
|
||||
checkouts,
|
||||
initialRef: candidateBranch,
|
||||
fetchRemote: Remote(
|
||||
name: RemoteName.upstream,
|
||||
url: engineUpstream,
|
||||
),
|
||||
pushRemote: Remote(
|
||||
name: RemoteName.mirror,
|
||||
url: engineMirror,
|
||||
),
|
||||
);
|
||||
// Create a new branch so that we don't accidentally push to upstream
|
||||
// candidateBranch.
|
||||
engine.newBranch('cherrypicks-$candidateBranch');
|
||||
final List<pb.Cherrypick> engineCherrypicks = _sortCherrypicks(
|
||||
repository: engine,
|
||||
cherrypicks: engineCherrypickRevisions,
|
||||
upstreamRef: EngineRepository.defaultBranch,
|
||||
releaseRef: candidateBranch,
|
||||
).map((String revision) => pb.Cherrypick(
|
||||
trunkRevision: revision,
|
||||
state: pb.CherrypickState.PENDING,
|
||||
)).toList();
|
||||
|
||||
for (final pb.Cherrypick cherrypick in engineCherrypicks) {
|
||||
final String revision = cherrypick.trunkRevision;
|
||||
final bool success = engine.canCherryPick(revision);
|
||||
stdio.printTrace(
|
||||
'Attempt to cherrypick $revision ${success ? 'succeeded' : 'failed'}',
|
||||
);
|
||||
if (!success) {
|
||||
cherrypick.state = pb.CherrypickState.PENDING_WITH_CONFLICT;
|
||||
}
|
||||
}
|
||||
final String engineHead = engine.reverseParse('HEAD');
|
||||
state.engine = pb.Repository(
|
||||
candidateBranch: candidateBranch,
|
||||
startingGitHead: engineHead,
|
||||
currentGitHead: engineHead,
|
||||
checkoutPath: engine.checkoutDirectory.path,
|
||||
cherrypicks: engineCherrypicks,
|
||||
);
|
||||
final FrameworkRepository framework = FrameworkRepository(
|
||||
checkouts,
|
||||
initialRef: candidateBranch,
|
||||
fetchRemote: Remote(
|
||||
name: RemoteName.upstream,
|
||||
url: frameworkUpstream,
|
||||
),
|
||||
pushRemote: Remote(
|
||||
name: RemoteName.mirror,
|
||||
url: frameworkMirror,
|
||||
),
|
||||
);
|
||||
framework.newBranch('cherrypicks-$candidateBranch');
|
||||
final List<pb.Cherrypick> frameworkCherrypicks = _sortCherrypicks(
|
||||
repository: framework,
|
||||
cherrypicks: frameworkCherrypickRevisions,
|
||||
upstreamRef: FrameworkRepository.defaultBranch,
|
||||
releaseRef: candidateBranch,
|
||||
).map((String revision) => pb.Cherrypick(
|
||||
trunkRevision: revision,
|
||||
state: pb.CherrypickState.PENDING,
|
||||
)).toList();
|
||||
|
||||
for (final pb.Cherrypick cherrypick in frameworkCherrypicks) {
|
||||
final String revision = cherrypick.trunkRevision;
|
||||
final bool result = framework.canCherryPick(revision);
|
||||
stdio.printTrace(
|
||||
'Attempt to cherrypick $cherrypick ${result ? 'succeeded' : 'failed'}',
|
||||
);
|
||||
}
|
||||
|
||||
final String frameworkHead = framework.reverseParse('HEAD');
|
||||
state.framework = pb.Repository(
|
||||
candidateBranch: candidateBranch,
|
||||
startingGitHead: frameworkHead,
|
||||
currentGitHead: frameworkHead,
|
||||
checkoutPath: framework.checkoutDirectory.path,
|
||||
cherrypicks: frameworkCherrypicks,
|
||||
);
|
||||
|
||||
state.lastPhase = ReleasePhase.INITIALIZE;
|
||||
|
||||
state.conductorVersion = conductorVersion;
|
||||
|
||||
stdio.printTrace('Writing state to file ${stateFile.path}...');
|
||||
|
||||
state.logs.addAll(stdio.logs);
|
||||
|
||||
stateFile.writeAsStringSync(
|
||||
jsonEncode(state.toProto3Json()),
|
||||
flush: true,
|
||||
);
|
||||
|
||||
stdio.printStatus(presentState(state));
|
||||
}
|
||||
|
||||
// To minimize merge conflicts, sort the commits by rev-list order.
|
||||
List<String> _sortCherrypicks({
|
||||
@required Repository repository,
|
||||
@required List<String> cherrypicks,
|
||||
@required String upstreamRef,
|
||||
@required String releaseRef,
|
||||
}) {
|
||||
if (cherrypicks.isEmpty) {
|
||||
return cherrypicks;
|
||||
}
|
||||
|
||||
// Input cherrypick hashes that failed to be parsed by git.
|
||||
final List<String> unknownCherrypicks = <String>[];
|
||||
// Full 40-char hashes parsed by git.
|
||||
final List<String> validatedCherrypicks = <String>[];
|
||||
// Final, validated, sorted list of cherrypicks to be applied.
|
||||
final List<String> sortedCherrypicks = <String>[];
|
||||
for (final String cherrypick in cherrypicks) {
|
||||
try {
|
||||
final String fullRef = repository.reverseParse(cherrypick);
|
||||
validatedCherrypicks.add(fullRef);
|
||||
} on GitException {
|
||||
// Catch this exception so that we can validate the rest.
|
||||
unknownCherrypicks.add(cherrypick);
|
||||
}
|
||||
}
|
||||
|
||||
final String branchPoint = repository.branchPoint(
|
||||
'${repository.fetchRemote.name}/$upstreamRef',
|
||||
'${repository.fetchRemote.name}/$releaseRef',
|
||||
);
|
||||
|
||||
// `git rev-list` returns newest first, so reverse this list
|
||||
final List<String> upstreamRevlist = repository.revList(<String>[
|
||||
'--ancestry-path',
|
||||
'$branchPoint..$upstreamRef',
|
||||
]).reversed.toList();
|
||||
|
||||
stdio.printStatus('upstreamRevList:\n${upstreamRevlist.join('\n')}\n');
|
||||
stdio.printStatus('validatedCherrypicks:\n${validatedCherrypicks.join('\n')}\n');
|
||||
for (final String upstreamRevision in upstreamRevlist) {
|
||||
if (validatedCherrypicks.contains(upstreamRevision)) {
|
||||
validatedCherrypicks.remove(upstreamRevision);
|
||||
sortedCherrypicks.add(upstreamRevision);
|
||||
if (unknownCherrypicks.isEmpty && validatedCherrypicks.isEmpty) {
|
||||
return sortedCherrypicks;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We were given input cherrypicks that were not present in the upstream
|
||||
// rev-list
|
||||
stdio.printError(
|
||||
'The following ${repository.name} cherrypicks were not found in the '
|
||||
'upstream $upstreamRef branch:',
|
||||
);
|
||||
for (final String cp in <String>[...validatedCherrypicks, ...unknownCherrypicks]) {
|
||||
stdio.printError('\t$cp');
|
||||
}
|
||||
throw ConductorException(
|
||||
'${validatedCherrypicks.length + unknownCherrypicks.length} unknown cherrypicks provided!',
|
||||
);
|
||||
}
|
||||
}
|
167
dev/tools/lib/state.dart
Normal file
167
dev/tools/lib/state.dart
Normal file
|
@ -0,0 +1,167 @@
|
|||
// 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:platform/platform.dart';
|
||||
|
||||
import './globals.dart';
|
||||
import './proto/conductor_state.pb.dart' as pb;
|
||||
import './proto/conductor_state.pbenum.dart' show ReleasePhase;
|
||||
|
||||
const String kStateFileName = '.flutter_conductor_state.json';
|
||||
|
||||
String luciConsoleLink(String channel, String groupName) {
|
||||
assert(
|
||||
<String>['stable', 'beta', 'dev', 'master'].contains(channel),
|
||||
'channel $channel not recognized',
|
||||
);
|
||||
assert(
|
||||
<String>['framework', 'engine', 'devicelab'].contains(groupName),
|
||||
'group named $groupName not recognized',
|
||||
);
|
||||
final String consoleName = channel == 'master' ? groupName : '${channel}_$groupName';
|
||||
return 'https://ci.chromium.org/p/flutter/g/$consoleName/console';
|
||||
}
|
||||
|
||||
String defaultStateFilePath(Platform platform) {
|
||||
assert(platform.environment['HOME'] != null);
|
||||
return <String>[
|
||||
platform.environment['HOME'],
|
||||
kStateFileName,
|
||||
].join(platform.pathSeparator);
|
||||
}
|
||||
|
||||
String presentState(pb.ConductorState state) {
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
buffer.writeln('Conductor version: ${state.conductorVersion}');
|
||||
buffer.writeln('Release channel: ${state.releaseChannel}');
|
||||
buffer.writeln('');
|
||||
buffer.writeln(
|
||||
'Release started at: ${DateTime.fromMillisecondsSinceEpoch(state.createdDate.toInt())}');
|
||||
buffer.writeln(
|
||||
'Last updated at: ${DateTime.fromMillisecondsSinceEpoch(state.lastUpdatedDate.toInt())}');
|
||||
buffer.writeln('');
|
||||
buffer.writeln('Engine Repo');
|
||||
buffer.writeln('\tCandidate branch: ${state.engine.candidateBranch}');
|
||||
buffer.writeln('\tStarting git HEAD: ${state.engine.startingGitHead}');
|
||||
buffer.writeln('\tCurrent git HEAD: ${state.engine.currentGitHead}');
|
||||
buffer.writeln('\tPath to checkout: ${state.engine.checkoutPath}');
|
||||
buffer.writeln('\tPost-submit LUCI dashboard: ${luciConsoleLink(state.releaseChannel, 'engine')}');
|
||||
if (state.engine.cherrypicks.isNotEmpty) {
|
||||
buffer.writeln('${state.engine.cherrypicks.length} Engine Cherrypicks:');
|
||||
for (final pb.Cherrypick cherrypick in state.engine.cherrypicks) {
|
||||
buffer.writeln('\t${cherrypick.trunkRevision} - ${cherrypick.state}');
|
||||
}
|
||||
} else {
|
||||
buffer.writeln('0 Engine cherrypicks.');
|
||||
}
|
||||
buffer.writeln('Framework Repo');
|
||||
buffer.writeln('\tCandidate branch: ${state.framework.candidateBranch}');
|
||||
buffer.writeln('\tStarting git HEAD: ${state.framework.startingGitHead}');
|
||||
buffer.writeln('\tCurrent git HEAD: ${state.framework.currentGitHead}');
|
||||
buffer.writeln('\tPath to checkout: ${state.framework.checkoutPath}');
|
||||
buffer.writeln('\tPost-submit LUCI dashboard: ${luciConsoleLink(state.releaseChannel, 'framework')}');
|
||||
buffer.writeln('\tDevicelab LUCI dashboard: ${luciConsoleLink(state.releaseChannel, 'devicelab')}');
|
||||
if (state.framework.cherrypicks.isNotEmpty) {
|
||||
buffer.writeln('${state.framework.cherrypicks.length} Framework Cherrypicks:');
|
||||
for (final pb.Cherrypick cherrypick in state.framework.cherrypicks) {
|
||||
buffer.writeln('\t${cherrypick.trunkRevision} - ${cherrypick.state}');
|
||||
}
|
||||
} else {
|
||||
buffer.writeln('0 Framework cherrypicks.');
|
||||
}
|
||||
buffer.writeln('');
|
||||
if (state.lastPhase == ReleasePhase.VERIFY_RELEASE) {
|
||||
buffer.writeln(
|
||||
'${state.releaseChannel} release ${state.releaseVersion} has been published and verified.\n',
|
||||
);
|
||||
return buffer.toString();
|
||||
}
|
||||
buffer.writeln('The next step is:');
|
||||
buffer.writeln(presentPhases(state.lastPhase));
|
||||
|
||||
buffer.writeln(phaseInstructions(state));
|
||||
buffer.writeln('');
|
||||
buffer.writeln('Issue `conductor next` when you are ready to proceed.');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
String presentPhases(ReleasePhase lastPhase) {
|
||||
final ReleasePhase nextPhase = getNextPhase(lastPhase);
|
||||
final StringBuffer buffer = StringBuffer();
|
||||
bool phaseCompleted = true;
|
||||
|
||||
for (final ReleasePhase phase in ReleasePhase.values) {
|
||||
if (phase == nextPhase) {
|
||||
// This phase will execute the next time `conductor next` is run.
|
||||
buffer.writeln('> ${phase.name} (next)');
|
||||
phaseCompleted = false;
|
||||
} else if (phaseCompleted) {
|
||||
// This phase was already completed.
|
||||
buffer.writeln('✓ ${phase.name}');
|
||||
} else {
|
||||
// This phase has not been completed yet.
|
||||
buffer.writeln(' ${phase.name}');
|
||||
}
|
||||
}
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
String phaseInstructions(pb.ConductorState state) {
|
||||
switch (state.lastPhase) {
|
||||
case ReleasePhase.INITIALIZE:
|
||||
if (state.engine.cherrypicks.isEmpty) {
|
||||
return <String>[
|
||||
'There are no engine cherrypicks, so issue `conductor next` to continue',
|
||||
'to the next step.',
|
||||
].join('\n');
|
||||
}
|
||||
return <String>[
|
||||
'You must now manually apply the following engine cherrypicks to the checkout',
|
||||
'at ${state.engine.checkoutPath} in order:',
|
||||
for (final pb.Cherrypick cherrypick in state.engine.cherrypicks)
|
||||
'\t${cherrypick.trunkRevision}',
|
||||
'See $kReleaseDocumentationUrl for more information.',
|
||||
].join('\n');
|
||||
case ReleasePhase.APPLY_ENGINE_CHERRYPICKS:
|
||||
return <String>[
|
||||
'You must verify Engine CI builds are successful and then codesign the',
|
||||
'binaries at revision ${state.engine.currentGitHead}.',
|
||||
].join('\n');
|
||||
case ReleasePhase.CODESIGN_ENGINE_BINARIES:
|
||||
return <String>[
|
||||
'You must now manually apply the following framework cherrypicks to the checkout',
|
||||
'at ${state.framework.checkoutPath} in order:',
|
||||
for (final pb.Cherrypick cherrypick in state.framework.cherrypicks)
|
||||
'\t${cherrypick.trunkRevision}',
|
||||
].join('\n');
|
||||
case ReleasePhase.APPLY_FRAMEWORK_CHERRYPICKS:
|
||||
return <String>[
|
||||
'You must verify Framework CI builds are successful.',
|
||||
'See $kReleaseDocumentationUrl for more information.',
|
||||
].join('\n');
|
||||
case ReleasePhase.PUBLISH_VERSION:
|
||||
return 'Issue `conductor next` to publish your release to the release branch.';
|
||||
case ReleasePhase.PUBLISH_CHANNEL:
|
||||
return <String>[
|
||||
'Release archive packages must be verified on cloud storage. Issue',
|
||||
'`conductor next` to check if they are ready.',
|
||||
].join('\n');
|
||||
case ReleasePhase.VERIFY_RELEASE:
|
||||
return 'This release has been completed.';
|
||||
}
|
||||
assert(false);
|
||||
return ''; // For analyzer
|
||||
}
|
||||
|
||||
/// Returns the next phase in the ReleasePhase enum.
|
||||
///
|
||||
/// Will throw a [ConductorException] if [ReleasePhase.RELEASE_VERIFIED] is
|
||||
/// passed as an argument, as there is no next phase.
|
||||
ReleasePhase getNextPhase(ReleasePhase previousPhase) {
|
||||
assert(previousPhase != null);
|
||||
if (previousPhase == ReleasePhase.VERIFY_RELEASE) {
|
||||
throw ConductorException('There is no next ReleasePhase!');
|
||||
}
|
||||
return ReleasePhase.valueOf(previousPhase.value + 1);
|
||||
}
|
68
dev/tools/lib/status.dart
Normal file
68
dev/tools/lib/status.dart
Normal file
|
@ -0,0 +1,68 @@
|
|||
// 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 'dart:convert' show jsonDecode;
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import './proto/conductor_state.pb.dart' as pb;
|
||||
import './repository.dart';
|
||||
import './state.dart';
|
||||
import './stdio.dart';
|
||||
|
||||
const String kVerboseFlag = 'verbose';
|
||||
const String kStateOption = 'state-file';
|
||||
|
||||
/// Command to print the status of the current Flutter release.
|
||||
class StatusCommand extends Command<void> {
|
||||
StatusCommand({
|
||||
@required this.checkouts,
|
||||
}) : platform = checkouts.platform,
|
||||
fileSystem = checkouts.fileSystem,
|
||||
stdio = checkouts.stdio {
|
||||
final String defaultPath = defaultStateFilePath(platform);
|
||||
argParser.addOption(
|
||||
kStateOption,
|
||||
defaultsTo: defaultPath,
|
||||
help: 'Path to persistent state file. Defaults to $defaultPath',
|
||||
);
|
||||
argParser.addFlag(
|
||||
kVerboseFlag,
|
||||
abbr: 'v',
|
||||
defaultsTo: false,
|
||||
help: 'Also print logs.',
|
||||
);
|
||||
}
|
||||
|
||||
final Checkouts checkouts;
|
||||
final FileSystem fileSystem;
|
||||
final Platform platform;
|
||||
final Stdio stdio;
|
||||
|
||||
@override
|
||||
String get name => 'status';
|
||||
|
||||
@override
|
||||
String get description => 'Print status of current release.';
|
||||
|
||||
@override
|
||||
void run() {
|
||||
final File stateFile = checkouts.fileSystem.file(argResults[kStateOption]);
|
||||
if (!stateFile.existsSync()) {
|
||||
stdio.printStatus(
|
||||
'No persistent state file found at ${argResults[kStateOption]}.');
|
||||
return;
|
||||
}
|
||||
final pb.ConductorState state = pb.ConductorState();
|
||||
state.mergeFromProto3Json(jsonDecode(stateFile.readAsStringSync()));
|
||||
stdio.printStatus(presentState(state));
|
||||
if (argResults[kVerboseFlag] as bool) {
|
||||
stdio.printStatus('\nLogs:');
|
||||
state.logs.forEach(stdio.printStatus);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -7,17 +7,31 @@ import 'dart:io' as io;
|
|||
import 'package:meta/meta.dart';
|
||||
|
||||
abstract class Stdio {
|
||||
final List<String> logs = <String>[];
|
||||
|
||||
/// Error/warning messages printed to STDERR.
|
||||
void printError(String message);
|
||||
@mustCallSuper
|
||||
void printError(String message) {
|
||||
logs.add('[error] $message');
|
||||
}
|
||||
|
||||
/// Ordinary STDOUT messages.
|
||||
void printStatus(String message);
|
||||
@mustCallSuper
|
||||
void printStatus(String message) {
|
||||
logs.add('[status] $message');
|
||||
}
|
||||
|
||||
/// Debug messages that are only printed in verbose mode.
|
||||
void printTrace(String message);
|
||||
@mustCallSuper
|
||||
void printTrace(String message) {
|
||||
logs.add('[trace] $message');
|
||||
}
|
||||
|
||||
/// Write string to STDOUT without trailing newline.
|
||||
void write(String message);
|
||||
@mustCallSuper
|
||||
void write(String message) {
|
||||
logs.add('[write] $message');
|
||||
}
|
||||
|
||||
/// Read a line of text from STDIN.
|
||||
String readLineSync();
|
||||
|
@ -43,21 +57,25 @@ class VerboseStdio extends Stdio {
|
|||
|
||||
@override
|
||||
void printError(String message) {
|
||||
super.printError(message);
|
||||
stderr.writeln(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void printStatus(String message) {
|
||||
super.printStatus(message);
|
||||
stdout.writeln(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void printTrace(String message) {
|
||||
super.printTrace(message);
|
||||
stdout.writeln(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void write(String message) {
|
||||
super.write(message);
|
||||
stdout.write(message);
|
||||
}
|
||||
|
||||
|
|
|
@ -12,12 +12,14 @@ dependencies:
|
|||
meta: 1.3.0
|
||||
path: 1.8.0
|
||||
process: 4.2.1
|
||||
protobuf: 1.1.3
|
||||
|
||||
charcode: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
clock: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
collection: 1.15.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
crypto: 3.0.1 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
file: 6.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
fixnum: 0.10.11 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
http_parser: 4.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
pedantic: 1.11.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
platform: 3.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
@ -63,4 +65,4 @@ dev_dependencies:
|
|||
webkit_inspection_protocol: 1.0.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
yaml: 3.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
||||
|
||||
# PUBSPEC CHECKSUM: 8750
|
||||
# PUBSPEC CHECKSUM: e555
|
||||
|
|
97
dev/tools/test/clean_test.dart
Normal file
97
dev/tools/test/clean_test.dart
Normal file
|
@ -0,0 +1,97 @@
|
|||
// 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:args/command_runner.dart';
|
||||
import 'package:dev_tools/clean.dart';
|
||||
import 'package:dev_tools/repository.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
|
||||
import './common.dart';
|
||||
|
||||
void main() {
|
||||
group('clean command', () {
|
||||
const String flutterRoot = '/flutter';
|
||||
const String checkoutsParentDirectory = '$flutterRoot/dev/tools/';
|
||||
|
||||
MemoryFileSystem fileSystem;
|
||||
FakePlatform platform;
|
||||
TestStdio stdio;
|
||||
FakeProcessManager processManager;
|
||||
|
||||
setUp(() {
|
||||
stdio = TestStdio();
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
});
|
||||
|
||||
tearDown(() {
|
||||
// Ensure these don't get re-used between tests
|
||||
stdio = null;
|
||||
fileSystem = null;
|
||||
processManager = null;
|
||||
platform = null;
|
||||
});
|
||||
|
||||
CommandRunner<void> createRunner({
|
||||
List<FakeCommand> commands,
|
||||
String operatingSystem,
|
||||
}) {
|
||||
operatingSystem ??= const LocalPlatform().operatingSystem;
|
||||
final String pathSeparator = operatingSystem == 'windows' ? r'\' : '/';
|
||||
|
||||
processManager = FakeProcessManager.list(commands ?? <FakeCommand>[]);
|
||||
platform = FakePlatform(
|
||||
environment: <String, String>{'HOME': '/path/to/user/home'},
|
||||
pathSeparator: pathSeparator,
|
||||
);
|
||||
final Checkouts checkouts = Checkouts(
|
||||
fileSystem: fileSystem,
|
||||
parentDirectory: fileSystem.directory(checkoutsParentDirectory),
|
||||
platform: platform,
|
||||
processManager: processManager,
|
||||
stdio: stdio,
|
||||
);
|
||||
final CleanCommand command = CleanCommand(
|
||||
checkouts: checkouts,
|
||||
);
|
||||
return CommandRunner<void>('clean-test', '')..addCommand(command);
|
||||
}
|
||||
|
||||
test('throws if no state file found', () async {
|
||||
final CommandRunner<void> runner = createRunner();
|
||||
const String stateFile = '/state-file.json';
|
||||
|
||||
await expectLater(
|
||||
() async => runner.run(<String>[
|
||||
'clean',
|
||||
'--$kStateOption',
|
||||
stateFile,
|
||||
'--$kYesFlag',
|
||||
]),
|
||||
throwsExceptionWith(
|
||||
'No persistent state file found at $stateFile',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('deletes state file', () async {
|
||||
final CommandRunner<void> runner = createRunner();
|
||||
final File stateFile = fileSystem.file('/state-file.json');
|
||||
stateFile.writeAsStringSync('{}');
|
||||
|
||||
await runner.run(<String>[
|
||||
'clean',
|
||||
'--$kStateOption',
|
||||
stateFile.path,
|
||||
'--$kYesFlag',
|
||||
]);
|
||||
|
||||
expect(stateFile.existsSync(), false);
|
||||
});
|
||||
}, onPlatform: <String, dynamic>{
|
||||
'windows': const Skip('Flutter Conductor only supported on macos/linux'),
|
||||
});
|
||||
}
|
|
@ -21,11 +21,14 @@ void main() {
|
|||
() async {
|
||||
const Platform platform = LocalPlatform();
|
||||
const FileSystem fileSystem = LocalFileSystem();
|
||||
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync(
|
||||
'conductor_integration_test',
|
||||
);
|
||||
const ProcessManager processManager = LocalProcessManager();
|
||||
final TestStdio stdio = TestStdio(verbose: true);
|
||||
final Checkouts checkouts = Checkouts(
|
||||
fileSystem: fileSystem,
|
||||
parentDirectory: localFlutterRoot.parent,
|
||||
parentDirectory: tempDir,
|
||||
platform: platform,
|
||||
processManager: processManager,
|
||||
stdio: stdio,
|
||||
|
|
|
@ -109,6 +109,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
'file://$flutterRoot/',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -194,6 +196,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
'file://$flutterRoot/',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -279,6 +283,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
'file://$flutterRoot/',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -336,6 +342,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
'file://$flutterRoot/',
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
|
|
@ -35,7 +35,7 @@ Matcher throwsExceptionWith(String messageSubString) {
|
|||
);
|
||||
}
|
||||
|
||||
class TestStdio implements Stdio {
|
||||
class TestStdio extends Stdio {
|
||||
TestStdio({
|
||||
this.verbose = false,
|
||||
List<String> stdin,
|
||||
|
@ -43,36 +43,15 @@ class TestStdio implements Stdio {
|
|||
_stdin = stdin ?? <String>[];
|
||||
}
|
||||
|
||||
final StringBuffer _error = StringBuffer();
|
||||
String get error => _error.toString();
|
||||
String get error => logs.where((String log) => log.startsWith(r'[error] ')).join('\n');
|
||||
|
||||
String get stdout => logs.where((String log) {
|
||||
return log.startsWith(r'[status] ') || log.startsWith(r'[trace] ');
|
||||
}).join('\n');
|
||||
|
||||
final StringBuffer _stdout = StringBuffer();
|
||||
String get stdout => _stdout.toString();
|
||||
final bool verbose;
|
||||
List<String> _stdin;
|
||||
|
||||
@override
|
||||
void printError(String message) {
|
||||
_error.writeln(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void printStatus(String message) {
|
||||
_stdout.writeln(message);
|
||||
}
|
||||
|
||||
@override
|
||||
void printTrace(String message) {
|
||||
if (verbose) {
|
||||
_stdout.writeln(message);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void write(String message) {
|
||||
_stdout.write(message);
|
||||
}
|
||||
|
||||
@override
|
||||
String readLineSync() {
|
||||
if (_stdin.isEmpty) {
|
||||
|
|
|
@ -7,7 +7,6 @@ import 'package:file/local.dart';
|
|||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import 'package:dev_tools/globals.dart';
|
||||
import 'package:dev_tools/roll_dev.dart' show rollDev;
|
||||
import 'package:dev_tools/repository.dart';
|
||||
import 'package:dev_tools/version.dart';
|
||||
|
@ -25,15 +24,17 @@ void main() {
|
|||
Checkouts checkouts;
|
||||
FrameworkRepository frameworkUpstream;
|
||||
FrameworkRepository framework;
|
||||
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: localFlutterRoot.parent,
|
||||
parentDirectory: tempDir,
|
||||
platform: platform,
|
||||
processManager: processManager,
|
||||
stdio: stdio,
|
||||
|
@ -45,7 +46,7 @@ void main() {
|
|||
framework = FrameworkRepository(
|
||||
checkouts,
|
||||
name: 'test-framework',
|
||||
upstream: 'file://${frameworkUpstream.checkoutDirectory.path}/',
|
||||
fetchRemote: Remote(name: RemoteName.upstream, url: 'file://${frameworkUpstream.checkoutDirectory.path}/'),
|
||||
);
|
||||
});
|
||||
|
||||
|
@ -59,7 +60,7 @@ void main() {
|
|||
commit: latestCommit,
|
||||
// Ensure this test passes after a dev release with hotfixes
|
||||
force: true,
|
||||
remote: 'origin',
|
||||
remote: 'upstream',
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -96,7 +97,7 @@ void main() {
|
|||
commit: latestCommit,
|
||||
// Ensure this test passes after a dev release with hotfixes
|
||||
force: true,
|
||||
remote: 'origin',
|
||||
remote: 'upstream',
|
||||
);
|
||||
|
||||
expect(
|
||||
|
@ -124,7 +125,7 @@ void main() {
|
|||
expect(finalVersion.m, 0);
|
||||
expect(finalVersion.n, 0);
|
||||
expect(finalVersion.commits, null);
|
||||
}, skip: 'TODO(fujino): https://github.com/flutter/flutter/issues/80463');
|
||||
});
|
||||
}, onPlatform: <String, dynamic>{
|
||||
'windows': const Skip('Flutter Conductor only supported on macos/linux'),
|
||||
});
|
||||
|
|
|
@ -83,6 +83,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
kUpstreamRemote,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -131,6 +133,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
kUpstreamRemote,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -193,16 +197,16 @@ void main() {
|
|||
),
|
||||
false,
|
||||
);
|
||||
expect(stdio.stdout.contains(nextVersion), true);
|
||||
expect(stdio.logs.join('').contains(nextVersion), true);
|
||||
});
|
||||
|
||||
test(
|
||||
'exits with exception if --skip-tagging is provided but commit isn\'t '
|
||||
'already tagged', () {
|
||||
test("exits with exception if --skip-tagging is provided but commit isn't already tagged", () {
|
||||
processManager.addCommands(<FakeCommand>[
|
||||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
kUpstreamRemote,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -283,6 +287,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
kUpstreamRemote,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -356,6 +362,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
kUpstreamRemote,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -433,6 +441,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
kUpstreamRemote,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -526,6 +536,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
kUpstreamRemote,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
@ -623,6 +635,8 @@ void main() {
|
|||
const FakeCommand(command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
kUpstreamRemote,
|
||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||
|
|
255
dev/tools/test/start_test.dart
Normal file
255
dev/tools/test/start_test.dart
Normal file
|
@ -0,0 +1,255 @@
|
|||
// 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 'dart:convert' show jsonDecode;
|
||||
|
||||
import 'package:args/command_runner.dart';
|
||||
import 'package:dev_tools/proto/conductor_state.pb.dart' as pb;
|
||||
import 'package:dev_tools/proto/conductor_state.pbenum.dart' show ReleasePhase;
|
||||
import 'package:dev_tools/start.dart';
|
||||
import 'package:dev_tools/state.dart';
|
||||
import 'package:dev_tools/repository.dart';
|
||||
import 'package:file/file.dart';
|
||||
import 'package:file/memory.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
|
||||
import '../../../packages/flutter_tools/test/src/fake_process_manager.dart';
|
||||
import './common.dart';
|
||||
|
||||
void main() {
|
||||
group('start command', () {
|
||||
const String flutterRoot = '/flutter';
|
||||
const String checkoutsParentDirectory = '$flutterRoot/dev/tools/';
|
||||
const String frameworkMirror = 'https://github.com/user/flutter.git';
|
||||
const String engineMirror = 'https://github.com/user/engine.git';
|
||||
const String candidateBranch = 'flutter-1.2-candidate.3';
|
||||
const String releaseChannel = 'stable';
|
||||
const String revision = 'abcd1234';
|
||||
Checkouts checkouts;
|
||||
MemoryFileSystem fileSystem;
|
||||
FakePlatform platform;
|
||||
TestStdio stdio;
|
||||
FakeProcessManager processManager;
|
||||
|
||||
setUp(() {
|
||||
stdio = TestStdio();
|
||||
fileSystem = MemoryFileSystem.test();
|
||||
});
|
||||
|
||||
CommandRunner<void> createRunner({
|
||||
Map<String, String> environment,
|
||||
String operatingSystem,
|
||||
List<FakeCommand> commands,
|
||||
}) {
|
||||
operatingSystem ??= const LocalPlatform().operatingSystem;
|
||||
final String pathSeparator = operatingSystem == 'windows' ? r'\' : '/';
|
||||
environment ??= <String, String>{
|
||||
'HOME': '/path/to/user/home',
|
||||
};
|
||||
final Directory homeDir = fileSystem.directory(
|
||||
environment['HOME'],
|
||||
);
|
||||
// Tool assumes this exists
|
||||
homeDir.createSync(recursive: true);
|
||||
platform = FakePlatform(
|
||||
environment: environment,
|
||||
operatingSystem: operatingSystem,
|
||||
pathSeparator: pathSeparator,
|
||||
);
|
||||
processManager = FakeProcessManager.list(commands ?? <FakeCommand>[]);
|
||||
checkouts = Checkouts(
|
||||
fileSystem: fileSystem,
|
||||
parentDirectory: fileSystem.directory(checkoutsParentDirectory),
|
||||
platform: platform,
|
||||
processManager: processManager,
|
||||
stdio: stdio,
|
||||
);
|
||||
final StartCommand command = StartCommand(
|
||||
checkouts: checkouts,
|
||||
flutterRoot: fileSystem.directory(flutterRoot),
|
||||
);
|
||||
return CommandRunner<void>('codesign-test', '')..addCommand(command);
|
||||
}
|
||||
|
||||
tearDown(() {
|
||||
// Ensure we don't re-use these between tests.
|
||||
processManager = null;
|
||||
checkouts = null;
|
||||
platform = null;
|
||||
});
|
||||
|
||||
test('throws exception if run from Windows', () async {
|
||||
final CommandRunner<void> runner = createRunner(
|
||||
commands: <FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||
stdout: revision,
|
||||
),
|
||||
],
|
||||
operatingSystem: 'windows',
|
||||
);
|
||||
await expectLater(
|
||||
() async => runner.run(<String>['start']),
|
||||
throwsExceptionWith(
|
||||
'Error! This tool is only supported on macOS and Linux',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('throws if --$kFrameworkMirrorOption not provided', () async {
|
||||
final CommandRunner<void> runner = createRunner(
|
||||
commands: <FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||
stdout: revision,
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
await expectLater(
|
||||
() async => runner.run(<String>['start']),
|
||||
throwsExceptionWith(
|
||||
'Expected either the CLI arg --$kFrameworkMirrorOption or the environment variable FRAMEWORK_MIRROR to be provided',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
test('creates state file if provided correct inputs', () async {
|
||||
const String revision2 = 'def789';
|
||||
const String revision3 = '123abc';
|
||||
|
||||
final List<FakeCommand> engineCommands = <FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
EngineRepository.defaultUpstream,
|
||||
fileSystem.path.join(
|
||||
checkoutsParentDirectory,
|
||||
'flutter_conductor_checkouts',
|
||||
'engine',
|
||||
),
|
||||
],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'remote', 'add', 'mirror', engineMirror],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'fetch', 'mirror'],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'checkout', 'upstream/$candidateBranch'],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||
stdout: revision2,
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
'-b',
|
||||
'cherrypicks-$candidateBranch',
|
||||
],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||
stdout: revision2,
|
||||
),
|
||||
];
|
||||
final List<FakeCommand> frameworkCommands = <FakeCommand>[
|
||||
FakeCommand(
|
||||
command: <String>[
|
||||
'git',
|
||||
'clone',
|
||||
'--origin',
|
||||
'upstream',
|
||||
'--',
|
||||
FrameworkRepository.defaultUpstream,
|
||||
fileSystem.path.join(
|
||||
checkoutsParentDirectory,
|
||||
'flutter_conductor_checkouts',
|
||||
'framework',
|
||||
),
|
||||
],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'remote', 'add', 'mirror', frameworkMirror],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'fetch', 'mirror'],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'checkout', 'upstream/$candidateBranch'],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||
stdout: revision3,
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>[
|
||||
'git',
|
||||
'checkout',
|
||||
'-b',
|
||||
'cherrypicks-$candidateBranch',
|
||||
],
|
||||
),
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||
stdout: revision3,
|
||||
),
|
||||
];
|
||||
final CommandRunner<void> runner = createRunner(
|
||||
commands: <FakeCommand>[
|
||||
const FakeCommand(
|
||||
command: <String>['git', 'rev-parse', 'HEAD'],
|
||||
stdout: revision,
|
||||
),
|
||||
...engineCommands,
|
||||
...frameworkCommands,
|
||||
],
|
||||
);
|
||||
|
||||
final String stateFilePath = fileSystem.path.join(
|
||||
platform.environment['HOME'],
|
||||
kStateFileName,
|
||||
);
|
||||
|
||||
await runner.run(<String>[
|
||||
'start',
|
||||
'--$kFrameworkMirrorOption',
|
||||
frameworkMirror,
|
||||
'--$kEngineMirrorOption',
|
||||
engineMirror,
|
||||
'--$kCandidateOption',
|
||||
candidateBranch,
|
||||
'--$kReleaseOption',
|
||||
releaseChannel,
|
||||
'--$kStateOption',
|
||||
stateFilePath,
|
||||
]);
|
||||
|
||||
final File stateFile = fileSystem.file(stateFilePath);
|
||||
|
||||
final pb.ConductorState state = pb.ConductorState();
|
||||
state.mergeFromProto3Json(
|
||||
jsonDecode(stateFile.readAsStringSync()),
|
||||
);
|
||||
|
||||
expect(state.isInitialized(), true);
|
||||
expect(state.releaseChannel, releaseChannel);
|
||||
expect(state.engine.candidateBranch, candidateBranch);
|
||||
expect(state.engine.startingGitHead, revision2);
|
||||
expect(state.framework.candidateBranch, candidateBranch);
|
||||
expect(state.framework.startingGitHead, revision3);
|
||||
expect(state.lastPhase, ReleasePhase.INITIALIZE);
|
||||
expect(state.conductorVersion, revision);
|
||||
});
|
||||
}, onPlatform: <String, dynamic>{
|
||||
'windows': const Skip('Flutter Conductor only supported on macos/linux'),
|
||||
});
|
||||
}
|
Loading…
Reference in a new issue