blob: 5eb1541033a22e4e0b005edf7526c17fa2c3ffd3 [file] [log] [blame]
// 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/args.dart';
import 'proto/conductor_state.pb.dart' as pb;
import 'repository.dart';
const String gsutilBinary = 'gsutil.py';
const String kFrameworkDefaultBranch = 'master';
const String kForceFlag = 'force';
const List<String> kBaseReleaseChannels = <String>['stable', 'beta'];
const List<String> kReleaseChannels = <String>[...kBaseReleaseChannels, FrameworkRepository.defaultBranch];
const String kReleaseDocumentationUrl = 'https://github.com/flutter/flutter/wiki/Flutter-Cherrypick-Process';
const String kLuciPackagingConsoleLink = 'https://ci.chromium.org/p/flutter/g/packaging/console';
const String kWebsiteReleasesUrl = 'https://docs.flutter.dev/development/tools/sdk/releases';
const String discordReleaseChannel =
'https://discord.com/channels/608014603317936148/783492179922124850';
const String flutterReleaseHotline =
'https://mail.google.com/chat/u/0/#chat/space/AAAA6RKcK2k';
const String hotfixToStableWiki =
'https://github.com/flutter/flutter/wiki/Hotfixes-to-the-Stable-Channel';
const String flutterAnnounceGroup =
'https://groups.google.com/g/flutter-announce';
const String hotfixDocumentationBestPractices =
'https://github.com/flutter/flutter/wiki/Hotfix-Documentation-Best-Practices';
final RegExp releaseCandidateBranchRegex = RegExp(
r'flutter-(\d+)\.(\d+)-candidate\.(\d+)',
);
/// Whether all releases published to the beta channel should be mirrored to
/// dev.
const bool kSynchronizeDevWithBeta = true;
/// Cast a dynamic to String and trim.
String stdoutToString(dynamic input) {
final String str = input as String;
return str.trim();
}
class ConductorException implements Exception {
ConductorException(this.message);
final String message;
@override
String toString() => 'Exception: $message';
}
bool assertsEnabled() {
// Verify asserts enabled
bool assertsEnabled = false;
assert(() {
assertsEnabled = true;
return true;
}());
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, {
bool allowNull = false,
}) {
final String envName = fromArgToEnvName(name);
if (env[envName] != null) {
return env[envName];
}
final String? argValue = argResults[name] as String?;
if (argValue != null) {
return argValue;
}
if (allowNull) {
return null;
}
throw ConductorException('Expected either the CLI arg --$name or the environment variable $envName '
'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.
///
/// 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'_');
}
/// Return a web link for the user to open a new PR.
///
/// Includes PR title and body via query params.
String getNewPrLink({
required String userName,
required String repoName,
required pb.ConductorState state,
}) {
assert(state.releaseChannel.isNotEmpty);
assert(state.releaseVersion.isNotEmpty);
late final String candidateBranch;
late final String workingBranch;
late final String repoLabel;
switch (repoName) {
case 'flutter':
candidateBranch = state.framework.candidateBranch;
workingBranch = state.framework.workingBranch;
repoLabel = 'Framework';
break;
case 'engine':
candidateBranch = state.engine.candidateBranch;
workingBranch = state.engine.workingBranch;
repoLabel = 'Engine';
break;
default:
throw ConductorException('Expected repoName to be one of flutter or engine but got $repoName.');
}
assert(candidateBranch.isNotEmpty);
assert(workingBranch.isNotEmpty);
final String title = '[flutter_releases] Flutter ${state.releaseChannel} '
'${state.releaseVersion} $repoLabel Cherrypicks';
final StringBuffer body = StringBuffer();
body.write('''
# Flutter ${state.releaseChannel} ${state.releaseVersion} $repoLabel
## Scheduled Cherrypicks
''');
if (repoName == 'engine') {
if (state.engine.dartRevision.isNotEmpty) {
// shorten hashes to make final link manageable
// prefix with github org/repo so GitHub will auto-generate a hyperlink
body.writeln('- Roll dart revision: dart-lang/sdk@${state.engine.dartRevision.substring(0, 9)}');
}
for (final pb.Cherrypick cp in state.engine.cherrypicks) {
// Only list commits that map to a commit that exists upstream.
if (cp.trunkRevision.isNotEmpty) {
body.writeln('- commit: flutter/engine@${cp.trunkRevision.substring(0, 9)}');
}
}
} else {
for (final pb.Cherrypick cp in state.framework.cherrypicks) {
// Only list commits that map to a commit that exists upstream.
if (cp.trunkRevision.isNotEmpty) {
body.writeln('- commit: ${cp.trunkRevision.substring(0, 9)}');
}
}
}
return 'https://github.com/flutter/$repoName/compare/'
'$candidateBranch...$userName:$workingBranch?'
'expand=1'
'&title=${Uri.encodeQueryComponent(title)}'
'&body=${Uri.encodeQueryComponent(body.toString())}';
}