mirror of
https://github.com/flutter/flutter
synced 2024-09-17 23:31:55 +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
|
unnecessary_null_comparison: ignore
|
||||||
exclude:
|
exclude:
|
||||||
- "bin/cache/**"
|
- "bin/cache/**"
|
||||||
|
# Ignore protoc generated files
|
||||||
|
- "dev/tools/lib/proto/*"
|
||||||
|
|
||||||
linter:
|
linter:
|
||||||
rules:
|
rules:
|
||||||
|
|
|
@ -2,18 +2,18 @@
|
||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
// Rolls the dev channel.
|
|
||||||
// Only tested on Linux.
|
|
||||||
//
|
|
||||||
// See: https://github.com/flutter/flutter/wiki/Release-process
|
// See: https://github.com/flutter/flutter/wiki/Release-process
|
||||||
|
|
||||||
import 'dart:io' as io;
|
import 'dart:io' as io;
|
||||||
|
|
||||||
import 'package:args/command_runner.dart';
|
import 'package:args/command_runner.dart';
|
||||||
|
import 'package:dev_tools/clean.dart';
|
||||||
import 'package:dev_tools/codesign.dart';
|
import 'package:dev_tools/codesign.dart';
|
||||||
import 'package:dev_tools/globals.dart';
|
import 'package:dev_tools/globals.dart';
|
||||||
import 'package:dev_tools/roll_dev.dart';
|
import 'package:dev_tools/roll_dev.dart';
|
||||||
import 'package:dev_tools/repository.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:dev_tools/stdio.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
|
@ -54,6 +54,16 @@ Future<void> main(List<String> args) async {
|
||||||
checkouts: checkouts,
|
checkouts: checkouts,
|
||||||
flutterRoot: localFlutterRoot,
|
flutterRoot: localFlutterRoot,
|
||||||
),
|
),
|
||||||
|
StatusCommand(
|
||||||
|
checkouts: checkouts,
|
||||||
|
),
|
||||||
|
StartCommand(
|
||||||
|
checkouts: checkouts,
|
||||||
|
flutterRoot: localFlutterRoot,
|
||||||
|
),
|
||||||
|
CleanCommand(
|
||||||
|
checkouts: checkouts,
|
||||||
|
),
|
||||||
].forEach(runner.addCommand);
|
].forEach(runner.addCommand);
|
||||||
|
|
||||||
if (!assertsEnabled()) {
|
if (!assertsEnabled()) {
|
||||||
|
@ -63,8 +73,8 @@ Future<void> main(List<String> args) async {
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await runner.run(args);
|
await runner.run(args);
|
||||||
} on Exception catch (e) {
|
} on Exception catch (e, stacktrace) {
|
||||||
stdio.printError(e.toString());
|
stdio.printError('$e\n\n$stacktrace');
|
||||||
io.exit(1);
|
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({
|
CodesignCommand({
|
||||||
@required this.checkouts,
|
@required this.checkouts,
|
||||||
@required this.flutterRoot,
|
@required this.flutterRoot,
|
||||||
|
FrameworkRepository framework,
|
||||||
}) : assert(flutterRoot != null),
|
}) : assert(flutterRoot != null),
|
||||||
fileSystem = checkouts.fileSystem,
|
fileSystem = checkouts.fileSystem,
|
||||||
platform = checkouts.platform,
|
platform = checkouts.platform,
|
||||||
stdio = checkouts.stdio,
|
stdio = checkouts.stdio,
|
||||||
processManager = checkouts.processManager {
|
processManager = checkouts.processManager {
|
||||||
|
if (framework != null) {
|
||||||
|
_framework = framework;
|
||||||
|
}
|
||||||
argParser.addFlag(
|
argParser.addFlag(
|
||||||
kVerify,
|
kVerify,
|
||||||
help:
|
help:
|
||||||
|
@ -71,13 +75,12 @@ class CodesignCommand extends Command<void> {
|
||||||
final Directory flutterRoot;
|
final Directory flutterRoot;
|
||||||
|
|
||||||
FrameworkRepository _framework;
|
FrameworkRepository _framework;
|
||||||
FrameworkRepository get framework => _framework ??= FrameworkRepository.localRepoAsUpstream(
|
FrameworkRepository get framework {
|
||||||
|
return _framework ??= FrameworkRepository.localRepoAsUpstream(
|
||||||
checkouts,
|
checkouts,
|
||||||
upstreamPath: flutterRoot.path,
|
upstreamPath: flutterRoot.path,
|
||||||
);
|
);
|
||||||
|
}
|
||||||
@visibleForTesting
|
|
||||||
set framework(FrameworkRepository framework) => _framework = framework;
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get name => 'codesign';
|
String get name => 'codesign';
|
||||||
|
@ -102,15 +105,19 @@ class CodesignCommand extends Command<void> {
|
||||||
|
|
||||||
String revision;
|
String revision;
|
||||||
if (argResults.wasParsed(kRevision)) {
|
if (argResults.wasParsed(kRevision)) {
|
||||||
stdio.printError('Warning! When providing an arbitrary revision, the contents of the cache may not');
|
stdio.printError(
|
||||||
stdio.printError('match the expected binaries in the conductor tool. It is preferred to check out');
|
'Warning! When providing an arbitrary revision, the contents of the cache may not');
|
||||||
stdio.printError('the desired revision and run that version of the conductor.\n');
|
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;
|
revision = argResults[kRevision] as String;
|
||||||
} else {
|
} else {
|
||||||
revision = (processManager.runSync(
|
revision = (processManager.runSync(
|
||||||
<String>['git', 'rev-parse', 'HEAD'],
|
<String>['git', 'rev-parse', 'HEAD'],
|
||||||
workingDirectory: framework.checkoutDirectory.path,
|
workingDirectory: framework.checkoutDirectory.path,
|
||||||
).stdout as String).trim();
|
).stdout as String)
|
||||||
|
.trim();
|
||||||
assert(revision.isNotEmpty);
|
assert(revision.isNotEmpty);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -158,7 +165,10 @@ class CodesignCommand extends Command<void> {
|
||||||
'dart-sdk/bin/dart',
|
'dart-sdk/bin/dart',
|
||||||
'dart-sdk/bin/dartaotruntime',
|
'dart-sdk/bin/dartaotruntime',
|
||||||
'dart-sdk/bin/utils/gen_snapshot',
|
'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.
|
/// 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-armv7_arm64/Flutter.framework/Flutter',
|
||||||
'artifacts/engine/ios/Flutter.xcframework/ios-x86_64-simulator/Flutter.framework/Flutter',
|
'artifacts/engine/ios/Flutter.xcframework/ios-x86_64-simulator/Flutter.framework/Flutter',
|
||||||
'artifacts/ios-deploy/ios-deploy',
|
'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.
|
/// Verify the existence of all expected binaries in cache.
|
||||||
|
@ -197,19 +210,27 @@ class CodesignCommand extends Command<void> {
|
||||||
} else if (binariesWithoutEntitlements.contains(binaryPath)) {
|
} else if (binariesWithoutEntitlements.contains(binaryPath)) {
|
||||||
foundFiles.add(binaryPath);
|
foundFiles.add(binaryPath);
|
||||||
} else {
|
} 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) {
|
if (foundFiles.length < allExpectedFiles.length) {
|
||||||
final List<String> unfoundFiles = allExpectedFiles.where(
|
final List<String> unfoundFiles = allExpectedFiles
|
||||||
|
.where(
|
||||||
(String file) => !foundFiles.contains(file),
|
(String file) => !foundFiles.contains(file),
|
||||||
).toList();
|
)
|
||||||
stdio.printError('Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n');
|
.toList();
|
||||||
stdio.printError('If this commit is removing binaries from the cache, this test should be fixed by');
|
stdio.printError(
|
||||||
stdio.printError('removing the relevant entry from either the `binariesWithEntitlements` or');
|
'Expected binaries not found in cache:\n\n${unfoundFiles.join('\n')}\n');
|
||||||
stdio.printError('`binariesWithoutEntitlements` getters in dev/tools/lib/codesign.dart.');
|
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!');
|
throw ConductorException('Did not find all expected binaries!');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -275,13 +296,15 @@ class CodesignCommand extends Command<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unexpectedBinaries.isNotEmpty) {
|
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);
|
unexpectedBinaries.forEach(print);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Finally, exit on any invalid state
|
// Finally, exit on any invalid state
|
||||||
if (unsignedBinaries.isNotEmpty) {
|
if (unsignedBinaries.isNotEmpty) {
|
||||||
throw ConductorException('Test failed because unsigned binaries detected.');
|
throw ConductorException(
|
||||||
|
'Test failed because unsigned binaries detected.');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (wrongEntitlementBinaries.isNotEmpty) {
|
if (wrongEntitlementBinaries.isNotEmpty) {
|
||||||
|
@ -291,7 +314,8 @@ class CodesignCommand extends Command<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (unexpectedBinaries.isNotEmpty) {
|
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(
|
stdio.printStatus(
|
||||||
|
@ -300,6 +324,7 @@ class CodesignCommand extends Command<void> {
|
||||||
}
|
}
|
||||||
|
|
||||||
List<String> _allBinaryPaths;
|
List<String> _allBinaryPaths;
|
||||||
|
|
||||||
/// Find every binary file in the given [rootDirectory].
|
/// Find every binary file in the given [rootDirectory].
|
||||||
List<String> findBinaryPaths(String rootDirectory) {
|
List<String> findBinaryPaths(String rootDirectory) {
|
||||||
if (_allBinaryPaths != null) {
|
if (_allBinaryPaths != null) {
|
||||||
|
@ -356,7 +381,8 @@ class CodesignCommand extends Command<void> {
|
||||||
bool passes = true;
|
bool passes = true;
|
||||||
final String output = entitlementResult.stdout as String;
|
final String output = entitlementResult.stdout as String;
|
||||||
for (final String entitlement in expectedEntitlements) {
|
for (final String entitlement in expectedEntitlements) {
|
||||||
final bool entitlementExpected = binariesWithEntitlements.contains(binaryPath);
|
final bool entitlementExpected =
|
||||||
|
binariesWithEntitlements.contains(binaryPath);
|
||||||
if (output.contains(entitlement) != entitlementExpected) {
|
if (output.contains(entitlement) != entitlementExpected) {
|
||||||
stdio.printError(
|
stdio.printError(
|
||||||
'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} '
|
'File "$binaryPath" ${entitlementExpected ? 'does not have expected' : 'has unexpected'} '
|
||||||
|
|
|
@ -19,6 +19,7 @@ class Git {
|
||||||
List<String> args,
|
List<String> args,
|
||||||
String explanation, {
|
String explanation, {
|
||||||
@required String workingDirectory,
|
@required String workingDirectory,
|
||||||
|
bool allowFailures = false,
|
||||||
}) {
|
}) {
|
||||||
final ProcessResult result = _run(args, workingDirectory);
|
final ProcessResult result = _run(args, workingDirectory);
|
||||||
if (result.exitCode == 0) {
|
if (result.exitCode == 0) {
|
||||||
|
@ -68,6 +69,15 @@ class Git {
|
||||||
message.writeln('stdout from git:\n${result.stdout}\n');
|
message.writeln('stdout from git:\n${result.stdout}\n');
|
||||||
if ((result.stderr as String).isNotEmpty)
|
if ((result.stderr as String).isNotEmpty)
|
||||||
message.writeln('stderr from git:\n${result.stderr}\n');
|
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
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:args/args.dart';
|
||||||
import 'package:file/file.dart';
|
import 'package:file/file.dart';
|
||||||
import 'package:file/local.dart';
|
import 'package:file/local.dart';
|
||||||
import 'package:platform/platform.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 kUpstreamRemote = 'https://github.com/flutter/flutter.git';
|
||||||
|
|
||||||
|
const String gsutilBinary = 'gsutil.py';
|
||||||
|
|
||||||
const List<String> kReleaseChannels = <String>[
|
const List<String> kReleaseChannels = <String>[
|
||||||
'stable',
|
'stable',
|
||||||
'beta',
|
'beta',
|
||||||
|
@ -23,6 +18,12 @@ const List<String> kReleaseChannels = <String>[
|
||||||
'master',
|
'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.
|
/// Cast a dynamic to String and trim.
|
||||||
String stdoutToString(dynamic input) {
|
String stdoutToString(dynamic input) {
|
||||||
final String str = input as String;
|
final String str = input as String;
|
||||||
|
@ -86,3 +87,64 @@ bool assertsEnabled() {
|
||||||
}());
|
}());
|
||||||
return 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 'package:platform/platform.dart';
|
||||||
|
|
||||||
import './git.dart';
|
import './git.dart';
|
||||||
import './globals.dart' as globals;
|
import './globals.dart';
|
||||||
import './stdio.dart';
|
import './stdio.dart';
|
||||||
import './version.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.
|
/// A source code repository.
|
||||||
abstract class Repository {
|
abstract class Repository {
|
||||||
Repository({
|
Repository({
|
||||||
@required this.name,
|
@required this.name,
|
||||||
@required this.upstream,
|
@required this.fetchRemote,
|
||||||
@required this.processManager,
|
@required this.processManager,
|
||||||
@required this.stdio,
|
@required this.stdio,
|
||||||
@required this.platform,
|
@required this.platform,
|
||||||
@required this.fileSystem,
|
@required this.fileSystem,
|
||||||
@required this.parentDirectory,
|
@required this.parentDirectory,
|
||||||
|
this.initialRef,
|
||||||
this.localUpstream = false,
|
this.localUpstream = false,
|
||||||
this.useExistingCheckout = false,
|
this.useExistingCheckout = false,
|
||||||
|
this.pushRemote,
|
||||||
}) : git = Git(processManager),
|
}) : git = Git(processManager),
|
||||||
assert(localUpstream != null),
|
assert(localUpstream != null),
|
||||||
assert(useExistingCheckout != null);
|
assert(useExistingCheckout != null);
|
||||||
|
|
||||||
final String name;
|
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 Git git;
|
||||||
final ProcessManager processManager;
|
final ProcessManager processManager;
|
||||||
final Stdio stdio;
|
final Stdio stdio;
|
||||||
|
@ -55,33 +95,49 @@ abstract class Repository {
|
||||||
return _checkoutDirectory;
|
return _checkoutDirectory;
|
||||||
}
|
}
|
||||||
_checkoutDirectory = parentDirectory.childDirectory(name);
|
_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()) {
|
if (!useExistingCheckout && _checkoutDirectory.existsSync()) {
|
||||||
stdio.printTrace('Deleting $name from ${_checkoutDirectory.path}...');
|
stdio.printTrace('Deleting $name from ${_checkoutDirectory.path}...');
|
||||||
_checkoutDirectory.deleteSync(recursive: true);
|
_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()) {
|
if (!_checkoutDirectory.existsSync()) {
|
||||||
stdio.printTrace(
|
stdio.printTrace(
|
||||||
'Cloning $name from $upstream to ${_checkoutDirectory.path}...');
|
'Cloning $name from ${fetchRemote.url} to ${_checkoutDirectory.path}...',
|
||||||
|
);
|
||||||
git.run(
|
git.run(
|
||||||
<String>['clone', '--', upstream, _checkoutDirectory.path],
|
<String>[
|
||||||
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
fetchRemote.name,
|
||||||
|
'--',
|
||||||
|
fetchRemote.url,
|
||||||
|
_checkoutDirectory.path
|
||||||
|
],
|
||||||
'Cloning $name repo',
|
'Cloning $name repo',
|
||||||
workingDirectory: parentDirectory.path,
|
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) {
|
if (localUpstream) {
|
||||||
// These branches must exist locally for the repo that depends on it
|
// These branches must exist locally for the repo that depends on it
|
||||||
// to fetch and push to.
|
// to fetch and push to.
|
||||||
for (final String channel in globals.kReleaseChannels) {
|
for (final String channel in kReleaseChannels) {
|
||||||
git.run(
|
git.run(
|
||||||
<String>['checkout', channel, '--'],
|
<String>['checkout', channel, '--'],
|
||||||
'check out branch $channel locally',
|
'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');
|
final String revision = reverseParse('HEAD');
|
||||||
stdio
|
stdio.printTrace(
|
||||||
.printTrace('Repository $name is checked out at revision "$revision".');
|
'Repository $name is checked out at revision "$revision".',
|
||||||
return _checkoutDirectory;
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The URL of the remote named [remoteName].
|
/// The URL of the remote named [remoteName].
|
||||||
|
@ -117,6 +180,15 @@ abstract class Repository {
|
||||||
return output == '';
|
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].
|
/// Fetch all branches and associated commits and tags from [remoteName].
|
||||||
void fetch(String remoteName) {
|
void fetch(String remoteName) {
|
||||||
git.run(
|
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(
|
git.run(
|
||||||
<String>['checkout', revision],
|
<String>['checkout', '-b', branchName],
|
||||||
'checkout $revision',
|
'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,
|
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].
|
/// Look up the commit for [ref].
|
||||||
String reverseParse(String ref) {
|
String reverseParse(String ref) {
|
||||||
final String revisionHash = git.getOutput(
|
final String revisionHash = git.getOutput(
|
||||||
<String>['rev-parse', ref],
|
<String>['rev-parse', ref],
|
||||||
'look up the commit for the ref $ref',
|
'look up the commit for the ref $ref',
|
||||||
workingDirectory: checkoutDirectory.path,
|
workingDirectory: checkoutDirectory.path,
|
||||||
).trim();
|
);
|
||||||
assert(revisionHash.isNotEmpty);
|
assert(revisionHash.isNotEmpty);
|
||||||
return revisionHash;
|
return revisionHash;
|
||||||
}
|
}
|
||||||
|
@ -184,11 +280,55 @@ abstract class Repository {
|
||||||
return exitcode == 0;
|
return exitcode == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Resets repository HEAD to [commit].
|
/// Determines if a commit will cherry-pick to current HEAD without conflict.
|
||||||
void reset(String commit) {
|
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(
|
git.run(
|
||||||
<String>['reset', commit, '--hard'],
|
<String>['cherry-pick', '--no-commit', commit],
|
||||||
'reset to the release 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,
|
workingDirectory: checkoutDirectory.path,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -260,12 +400,17 @@ class FrameworkRepository extends Repository {
|
||||||
FrameworkRepository(
|
FrameworkRepository(
|
||||||
this.checkouts, {
|
this.checkouts, {
|
||||||
String name = 'framework',
|
String name = 'framework',
|
||||||
String upstream = FrameworkRepository.defaultUpstream,
|
Remote fetchRemote = const Remote(
|
||||||
|
name: RemoteName.upstream, url: FrameworkRepository.defaultUpstream),
|
||||||
bool localUpstream = false,
|
bool localUpstream = false,
|
||||||
bool useExistingCheckout = false,
|
bool useExistingCheckout = false,
|
||||||
|
String initialRef,
|
||||||
|
Remote pushRemote,
|
||||||
}) : super(
|
}) : super(
|
||||||
name: name,
|
name: name,
|
||||||
upstream: upstream,
|
fetchRemote: fetchRemote,
|
||||||
|
pushRemote: pushRemote,
|
||||||
|
initialRef: initialRef,
|
||||||
fileSystem: checkouts.fileSystem,
|
fileSystem: checkouts.fileSystem,
|
||||||
localUpstream: localUpstream,
|
localUpstream: localUpstream,
|
||||||
parentDirectory: checkouts.directory,
|
parentDirectory: checkouts.directory,
|
||||||
|
@ -288,7 +433,10 @@ class FrameworkRepository extends Repository {
|
||||||
return FrameworkRepository(
|
return FrameworkRepository(
|
||||||
checkouts,
|
checkouts,
|
||||||
name: name,
|
name: name,
|
||||||
upstream: 'file://$upstreamPath/',
|
fetchRemote: Remote(
|
||||||
|
name: RemoteName.upstream,
|
||||||
|
url: 'file://$upstreamPath/',
|
||||||
|
),
|
||||||
localUpstream: false,
|
localUpstream: false,
|
||||||
useExistingCheckout: useExistingCheckout,
|
useExistingCheckout: useExistingCheckout,
|
||||||
);
|
);
|
||||||
|
@ -298,6 +446,8 @@ class FrameworkRepository extends Repository {
|
||||||
static const String defaultUpstream =
|
static const String defaultUpstream =
|
||||||
'https://github.com/flutter/flutter.git';
|
'https://github.com/flutter/flutter.git';
|
||||||
|
|
||||||
|
static const String defaultBranch = 'master';
|
||||||
|
|
||||||
String get cacheDirectory => fileSystem.path.join(
|
String get cacheDirectory => fileSystem.path.join(
|
||||||
checkoutDirectory.path,
|
checkoutDirectory.path,
|
||||||
'bin',
|
'bin',
|
||||||
|
@ -311,7 +461,8 @@ class FrameworkRepository extends Repository {
|
||||||
return FrameworkRepository(
|
return FrameworkRepository(
|
||||||
checkouts,
|
checkouts,
|
||||||
name: cloneName,
|
name: cloneName,
|
||||||
upstream: 'file://${checkoutDirectory.path}/',
|
fetchRemote: Remote(
|
||||||
|
name: RemoteName.upstream, url: 'file://${checkoutDirectory.path}/'),
|
||||||
useExistingCheckout: useExistingCheckout,
|
useExistingCheckout: useExistingCheckout,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -345,8 +496,8 @@ class FrameworkRepository extends Repository {
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void checkout(String revision) {
|
void checkout(String ref) {
|
||||||
super.checkout(revision);
|
super.checkout(ref);
|
||||||
// The tool will overwrite old cached artifacts, but not delete unused
|
// The tool will overwrite old cached artifacts, but not delete unused
|
||||||
// artifacts from a previous version. Thus, delete the entire cache and
|
// artifacts from a previous version. Thus, delete the entire cache and
|
||||||
// re-populate.
|
// re-populate.
|
||||||
|
@ -363,12 +514,55 @@ class FrameworkRepository extends Repository {
|
||||||
final io.ProcessResult result =
|
final io.ProcessResult result =
|
||||||
runFlutter(<String>['--version', '--machine']);
|
runFlutter(<String>['--version', '--machine']);
|
||||||
final Map<String, dynamic> versionJson = jsonDecode(
|
final Map<String, dynamic> versionJson = jsonDecode(
|
||||||
globals.stdoutToString(result.stdout),
|
stdoutToString(result.stdout),
|
||||||
) as Map<String, dynamic>;
|
) as Map<String, dynamic>;
|
||||||
return Version.fromString(versionJson['frameworkVersion'] as String);
|
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.
|
/// An enum of all the repositories that the Conductor supports.
|
||||||
enum RepositoryType {
|
enum RepositoryType {
|
||||||
framework,
|
framework,
|
||||||
|
|
|
@ -8,11 +8,18 @@ import 'package:file/file.dart';
|
||||||
import 'package:meta/meta.dart';
|
import 'package:meta/meta.dart';
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
|
|
||||||
import './globals.dart';
|
|
||||||
import './repository.dart';
|
import './repository.dart';
|
||||||
import './stdio.dart';
|
import './stdio.dart';
|
||||||
import './version.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.
|
/// Create a new dev release without cherry picks.
|
||||||
class RollDevCommand extends Command<void> {
|
class RollDevCommand extends Command<void> {
|
||||||
RollDevCommand({
|
RollDevCommand({
|
||||||
|
@ -57,7 +64,17 @@ class RollDevCommand extends Command<void> {
|
||||||
help: 'Do not create tag and push to remote, only update release branch. '
|
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.'
|
'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;
|
final Checkouts checkouts;
|
||||||
|
@ -92,8 +109,8 @@ bool rollDev({
|
||||||
@required ArgResults argResults,
|
@required ArgResults argResults,
|
||||||
@required Stdio stdio,
|
@required Stdio stdio,
|
||||||
@required FrameworkRepository repository,
|
@required FrameworkRepository repository,
|
||||||
String remoteName = 'origin',
|
|
||||||
}) {
|
}) {
|
||||||
|
final String remoteName = argResults[kRemoteName] as String;
|
||||||
final String level = argResults[kIncrement] as String;
|
final String level = argResults[kIncrement] as String;
|
||||||
final String commit = argResults[kCommit] as String;
|
final String commit = argResults[kCommit] as String;
|
||||||
final bool justPrint = argResults[kJustPrint] as bool;
|
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';
|
import 'package:meta/meta.dart';
|
||||||
|
|
||||||
abstract class Stdio {
|
abstract class Stdio {
|
||||||
|
final List<String> logs = <String>[];
|
||||||
|
|
||||||
/// Error/warning messages printed to STDERR.
|
/// Error/warning messages printed to STDERR.
|
||||||
void printError(String message);
|
@mustCallSuper
|
||||||
|
void printError(String message) {
|
||||||
|
logs.add('[error] $message');
|
||||||
|
}
|
||||||
|
|
||||||
/// Ordinary STDOUT messages.
|
/// 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.
|
/// 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.
|
/// 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.
|
/// Read a line of text from STDIN.
|
||||||
String readLineSync();
|
String readLineSync();
|
||||||
|
@ -43,21 +57,25 @@ class VerboseStdio extends Stdio {
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void printError(String message) {
|
void printError(String message) {
|
||||||
|
super.printError(message);
|
||||||
stderr.writeln(message);
|
stderr.writeln(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void printStatus(String message) {
|
void printStatus(String message) {
|
||||||
|
super.printStatus(message);
|
||||||
stdout.writeln(message);
|
stdout.writeln(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void printTrace(String message) {
|
void printTrace(String message) {
|
||||||
|
super.printTrace(message);
|
||||||
stdout.writeln(message);
|
stdout.writeln(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void write(String message) {
|
void write(String message) {
|
||||||
|
super.write(message);
|
||||||
stdout.write(message);
|
stdout.write(message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,12 +12,14 @@ dependencies:
|
||||||
meta: 1.3.0
|
meta: 1.3.0
|
||||||
path: 1.8.0
|
path: 1.8.0
|
||||||
process: 4.2.1
|
process: 4.2.1
|
||||||
|
protobuf: 1.1.3
|
||||||
|
|
||||||
charcode: 1.2.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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"
|
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 {
|
() async {
|
||||||
const Platform platform = LocalPlatform();
|
const Platform platform = LocalPlatform();
|
||||||
const FileSystem fileSystem = LocalFileSystem();
|
const FileSystem fileSystem = LocalFileSystem();
|
||||||
|
final Directory tempDir = fileSystem.systemTempDirectory.createTempSync(
|
||||||
|
'conductor_integration_test',
|
||||||
|
);
|
||||||
const ProcessManager processManager = LocalProcessManager();
|
const ProcessManager processManager = LocalProcessManager();
|
||||||
final TestStdio stdio = TestStdio(verbose: true);
|
final TestStdio stdio = TestStdio(verbose: true);
|
||||||
final Checkouts checkouts = Checkouts(
|
final Checkouts checkouts = Checkouts(
|
||||||
fileSystem: fileSystem,
|
fileSystem: fileSystem,
|
||||||
parentDirectory: localFlutterRoot.parent,
|
parentDirectory: tempDir,
|
||||||
platform: platform,
|
platform: platform,
|
||||||
processManager: processManager,
|
processManager: processManager,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
|
|
|
@ -109,6 +109,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
'file://$flutterRoot/',
|
'file://$flutterRoot/',
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -194,6 +196,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
'file://$flutterRoot/',
|
'file://$flutterRoot/',
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -279,6 +283,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
'file://$flutterRoot/',
|
'file://$flutterRoot/',
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -336,6 +342,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
'file://$flutterRoot/',
|
'file://$flutterRoot/',
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
|
|
@ -35,7 +35,7 @@ Matcher throwsExceptionWith(String messageSubString) {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class TestStdio implements Stdio {
|
class TestStdio extends Stdio {
|
||||||
TestStdio({
|
TestStdio({
|
||||||
this.verbose = false,
|
this.verbose = false,
|
||||||
List<String> stdin,
|
List<String> stdin,
|
||||||
|
@ -43,36 +43,15 @@ class TestStdio implements Stdio {
|
||||||
_stdin = stdin ?? <String>[];
|
_stdin = stdin ?? <String>[];
|
||||||
}
|
}
|
||||||
|
|
||||||
final StringBuffer _error = StringBuffer();
|
String get error => logs.where((String log) => log.startsWith(r'[error] ')).join('\n');
|
||||||
String get error => _error.toString();
|
|
||||||
|
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;
|
final bool verbose;
|
||||||
List<String> _stdin;
|
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
|
@override
|
||||||
String readLineSync() {
|
String readLineSync() {
|
||||||
if (_stdin.isEmpty) {
|
if (_stdin.isEmpty) {
|
||||||
|
|
|
@ -7,7 +7,6 @@ import 'package:file/local.dart';
|
||||||
import 'package:platform/platform.dart';
|
import 'package:platform/platform.dart';
|
||||||
import 'package:process/process.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/roll_dev.dart' show rollDev;
|
||||||
import 'package:dev_tools/repository.dart';
|
import 'package:dev_tools/repository.dart';
|
||||||
import 'package:dev_tools/version.dart';
|
import 'package:dev_tools/version.dart';
|
||||||
|
@ -25,15 +24,17 @@ void main() {
|
||||||
Checkouts checkouts;
|
Checkouts checkouts;
|
||||||
FrameworkRepository frameworkUpstream;
|
FrameworkRepository frameworkUpstream;
|
||||||
FrameworkRepository framework;
|
FrameworkRepository framework;
|
||||||
|
Directory tempDir;
|
||||||
|
|
||||||
setUp(() {
|
setUp(() {
|
||||||
platform = const LocalPlatform();
|
platform = const LocalPlatform();
|
||||||
fileSystem = const LocalFileSystem();
|
fileSystem = const LocalFileSystem();
|
||||||
processManager = const LocalProcessManager();
|
processManager = const LocalProcessManager();
|
||||||
stdio = TestStdio(verbose: true);
|
stdio = TestStdio(verbose: true);
|
||||||
|
tempDir = fileSystem.systemTempDirectory.createTempSync('flutter_conductor_checkouts');
|
||||||
checkouts = Checkouts(
|
checkouts = Checkouts(
|
||||||
fileSystem: fileSystem,
|
fileSystem: fileSystem,
|
||||||
parentDirectory: localFlutterRoot.parent,
|
parentDirectory: tempDir,
|
||||||
platform: platform,
|
platform: platform,
|
||||||
processManager: processManager,
|
processManager: processManager,
|
||||||
stdio: stdio,
|
stdio: stdio,
|
||||||
|
@ -45,7 +46,7 @@ void main() {
|
||||||
framework = FrameworkRepository(
|
framework = FrameworkRepository(
|
||||||
checkouts,
|
checkouts,
|
||||||
name: 'test-framework',
|
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,
|
commit: latestCommit,
|
||||||
// Ensure this test passes after a dev release with hotfixes
|
// Ensure this test passes after a dev release with hotfixes
|
||||||
force: true,
|
force: true,
|
||||||
remote: 'origin',
|
remote: 'upstream',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -96,7 +97,7 @@ void main() {
|
||||||
commit: latestCommit,
|
commit: latestCommit,
|
||||||
// Ensure this test passes after a dev release with hotfixes
|
// Ensure this test passes after a dev release with hotfixes
|
||||||
force: true,
|
force: true,
|
||||||
remote: 'origin',
|
remote: 'upstream',
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(
|
expect(
|
||||||
|
@ -124,7 +125,7 @@ void main() {
|
||||||
expect(finalVersion.m, 0);
|
expect(finalVersion.m, 0);
|
||||||
expect(finalVersion.n, 0);
|
expect(finalVersion.n, 0);
|
||||||
expect(finalVersion.commits, null);
|
expect(finalVersion.commits, null);
|
||||||
}, skip: 'TODO(fujino): https://github.com/flutter/flutter/issues/80463');
|
});
|
||||||
}, onPlatform: <String, dynamic>{
|
}, onPlatform: <String, dynamic>{
|
||||||
'windows': const Skip('Flutter Conductor only supported on macos/linux'),
|
'windows': const Skip('Flutter Conductor only supported on macos/linux'),
|
||||||
});
|
});
|
||||||
|
|
|
@ -83,6 +83,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -131,6 +133,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -193,16 +197,16 @@ void main() {
|
||||||
),
|
),
|
||||||
false,
|
false,
|
||||||
);
|
);
|
||||||
expect(stdio.stdout.contains(nextVersion), true);
|
expect(stdio.logs.join('').contains(nextVersion), true);
|
||||||
});
|
});
|
||||||
|
|
||||||
test(
|
test("exits with exception if --skip-tagging is provided but commit isn't already tagged", () {
|
||||||
'exits with exception if --skip-tagging is provided but commit isn\'t '
|
|
||||||
'already tagged', () {
|
|
||||||
processManager.addCommands(<FakeCommand>[
|
processManager.addCommands(<FakeCommand>[
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -283,6 +287,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -356,6 +362,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -433,6 +441,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -526,6 +536,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
||||||
|
@ -623,6 +635,8 @@ void main() {
|
||||||
const FakeCommand(command: <String>[
|
const FakeCommand(command: <String>[
|
||||||
'git',
|
'git',
|
||||||
'clone',
|
'clone',
|
||||||
|
'--origin',
|
||||||
|
'upstream',
|
||||||
'--',
|
'--',
|
||||||
kUpstreamRemote,
|
kUpstreamRemote,
|
||||||
'${checkoutsParentDirectory}flutter_conductor_checkouts/framework',
|
'${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