Unverified Commit 764577f8 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

[flutter_conductor] allow --force to override validations on start sub-command (#93514)

parent e95d669e
......@@ -9,6 +9,7 @@ import 'proto/conductor_state.pb.dart' as pb;
const String gsutilBinary = 'gsutil.py';
const String kFrameworkDefaultBranch = 'master';
const String kForceFlag = 'force';
const List<String> kReleaseChannels = <String>[
'stable',
......@@ -81,6 +82,18 @@ String? getValueFromEnvOrArgs(
'to be provided!');
}
bool getBoolFromEnvOrArgs(
String name,
ArgResults argResults,
Map<String, String> env,
) {
final String envName = fromArgToEnvName(name);
if (env[envName] != null) {
return (env[envName]?.toUpperCase()) == 'TRUE';
}
return argResults[name] as bool;
}
/// Return multiple values from the environment or fall back to [argResults].
///
/// Values read from an environment variable are assumed to be comma-delimited.
......
......@@ -14,7 +14,6 @@ import './stdio.dart';
const String kStateOption = 'state-file';
const String kYesFlag = 'yes';
const String kForceFlag = 'force';
/// Command to proceed from one [pb.ReleasePhase] to the next.
class NextCommand extends Command<void> {
......
......@@ -102,6 +102,11 @@ class StartCommand extends Command<void> {
'n': 'Indicates a hotfix to a dev or beta release.',
},
);
argParser.addFlag(
kForceFlag,
abbr: 'f',
help: 'Override all validations of the command line inputs.',
);
}
final Checkouts checkouts;
......@@ -178,6 +183,11 @@ class StartCommand extends Command<void> {
argumentResults,
platform.environment,
)!;
final bool force = getBoolFromEnvOrArgs(
kForceFlag,
argumentResults,
platform.environment,
);
final File stateFile = checkouts.fileSystem.file(
getValueFromEnvOrArgs(kStateOption, argumentResults, platform.environment),
);
......@@ -198,6 +208,7 @@ class StartCommand extends Command<void> {
releaseChannel: releaseChannel,
stateFile: stateFile,
stdio: stdio,
force: force,
);
return context.run();
}
......@@ -223,6 +234,7 @@ class StartContext {
required this.releaseChannel,
required this.stateFile,
required this.stdio,
this.force = false,
}) : git = Git(processManager);
final String candidateBranch;
......@@ -242,6 +254,9 @@ class StartContext {
final File stateFile;
final Stdio stdio;
/// If validations should be overridden.
final bool force;
Future<void> run() async {
if (stateFile.existsSync()) {
throw ConductorException(
......@@ -361,7 +376,12 @@ class StartContext {
final Version lastVersion = Version.fromString(await framework.getFullTag(
framework.upstreamRemote.name, candidateBranch,
exact: false,
))..ensureValid(candidateBranch, incrementLetter);
));
// [force] means we know this would fail but need to publish anyway
if (!force) {
lastVersion.ensureValid(candidateBranch, incrementLetter);
}
Version nextVersion = calculateNextVersion(lastVersion);
nextVersion = await ensureBranchPointTagged(nextVersion, framework);
......
......@@ -238,12 +238,18 @@ class Version {
final int y;
/// Number of hotfix releases after a stable release.
///
/// For non-stable releases, this will be 0.
final int z;
/// Zero-indexed count of dev releases after a beta release.
///
/// For stable releases, this will be null.
final int? m;
/// Number of hotfixes required to make a dev release.
///
/// For stable releases, this will be null.
final int? n;
/// Number of commits past last tagged dev release.
......
......@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:args/args.dart';
import 'package:conductor_core/src/globals.dart';
import 'package:conductor_core/src/proto/conductor_state.pb.dart' as pb;
......@@ -129,4 +130,69 @@ void main() {
);
});
});
group('getBoolFromEnvOrArgs', () {
const String flagName = 'a-cli-flag';
test('prefers env over argResults', () {
final ArgResults argResults = FakeArgs(results: <String, Object>{
flagName: false,
});
final Map<String, String> env = <String, String>{'A_CLI_FLAG': 'TRUE'};
final bool result = getBoolFromEnvOrArgs(
flagName,
argResults,
env,
);
expect(result, true);
});
test('falls back to argResults if env is empty', () {
final ArgResults argResults = FakeArgs(results: <String, Object>{
flagName: false,
});
final Map<String, String> env = <String, String>{};
final bool result = getBoolFromEnvOrArgs(
flagName,
argResults,
env,
);
expect(result, false);
});
});
}
class FakeArgs implements ArgResults {
FakeArgs({
this.arguments = const <String>[],
this.name = 'fake-command',
this.results = const <String, Object>{},
});
final Map<String, Object> results;
@override
final List<String> arguments;
@override
final String name;
@override
ArgResults? get command => throw Exception('Unimplemented');
@override
List<String> get rest => throw Exception('Unimplemented');
@override
Iterable<String> get options => throw Exception('Unimplemented');
@override
bool wasParsed(String name) {
return results[name] != null;
}
@override
Object? operator[](String name) {
return results[name];
}
}
......@@ -5,6 +5,7 @@
import 'dart:convert' show jsonDecode;
import 'package:args/command_runner.dart';
import 'package:conductor_core/src/globals.dart';
import 'package:conductor_core/src/proto/conductor_state.pb.dart' as pb;
import 'package:conductor_core/src/proto/conductor_state.pbenum.dart' show ReleasePhase;
import 'package:conductor_core/src/repository.dart';
......@@ -122,6 +123,188 @@ void main() {
);
});
test('does not fail if version wrong but --force provided', () async {
const String revision2 = 'def789';
const String revision3 = '123abc';
const String previousDartRevision = '171876a4e6cf56ee6da1f97d203926bd7afda7ef';
const String nextDartRevision = 'f6c91128be6b77aef8351e1e3a9d07c85bc2e46e';
const String previousVersion = '1.2.0-1.0.pre';
// This is what this release will be
const String nextVersion = '1.2.0-1.1.pre';
const String incrementLevel = 'n';
final Directory engine = fileSystem.directory(checkoutsParentDirectory)
.childDirectory('flutter_conductor_checkouts')
.childDirectory('engine');
final File depsFile = engine.childFile('DEPS');
final List<FakeCommand> engineCommands = <FakeCommand>[
FakeCommand(
command: <String>[
'git',
'clone',
'--origin',
'upstream',
'--',
EngineRepository.defaultUpstream,
engine.path,
],
onRun: () {
// Create the DEPS file which the tool will update
engine.createSync(recursive: true);
depsFile.writeAsStringSync(generateMockDeps(previousDartRevision));
}
),
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', 'status', '--porcelain'],
stdout: 'MM path/to/DEPS',
),
const FakeCommand(
command: <String>['git', 'add', '--all'],
),
const FakeCommand(
command: <String>['git', 'commit', "--message='Update Dart SDK to $nextDartRevision'"],
),
const FakeCommand(
command: <String>['git', 'rev-parse', 'HEAD'],
stdout: revision2,
),
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',
'describe',
'--match',
'*.*.*',
'--tags',
'refs/remotes/upstream/$candidateBranch',
],
stdout: '$previousVersion-42-gabc123',
),
const FakeCommand(
command: <String>['git', 'rev-parse', 'HEAD'],
stdout: revision3,
),
];
final CommandRunner<void> runner = createRunner(
commands: <FakeCommand>[
...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,
'--$kDartRevisionOption',
nextDartRevision,
'--$kIncrementOption',
incrementLevel,
'--$kForceFlag',
]);
final File stateFile = fileSystem.file(stateFilePath);
final pb.ConductorState state = pb.ConductorState();
state.mergeFromProto3Json(
jsonDecode(stateFile.readAsStringSync()),
);
expect(processManager.hasRemainingExpectations, false);
expect(state.isInitialized(), true);
expect(state.releaseChannel, releaseChannel);
expect(state.releaseVersion, nextVersion);
expect(state.engine.candidateBranch, candidateBranch);
expect(state.engine.startingGitHead, revision2);
expect(state.engine.dartRevision, nextDartRevision);
expect(state.engine.upstream.url, 'git@github.com:flutter/engine.git');
expect(state.framework.candidateBranch, candidateBranch);
expect(state.framework.startingGitHead, revision3);
expect(state.framework.upstream.url, 'git@github.com:flutter/flutter.git');
expect(state.currentPhase, ReleasePhase.APPLY_ENGINE_CHERRYPICKS);
expect(state.conductorVersion, conductorVersion);
expect(state.incrementLevel, incrementLevel);
});
test('creates state file if provided correct inputs', () async {
const String revision2 = 'def789';
const String revision3 = '123abc';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment