globals.dart 6.45 KB
Newer Older
1 2 3 4
// 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.

5
import 'package:args/args.dart';
6

7
import 'proto/conductor_state.pb.dart' as pb;
8
import 'repository.dart';
9

10 11
const String gsutilBinary = 'gsutil.py';

12
const String kFrameworkDefaultBranch = 'master';
13
const String kForceFlag = 'force';
14

15
const List<String> kBaseReleaseChannels = <String>['stable', 'beta'];
16

17
const List<String> kReleaseChannels = <String>[...kBaseReleaseChannels, FrameworkRepository.defaultBranch];
18

19
const String kReleaseDocumentationUrl = 'https://github.com/flutter/flutter/wiki/Flutter-Cherrypick-Process';
20

21
const String kLuciPackagingConsoleLink = 'https://ci.chromium.org/p/flutter/g/packaging/console';
22

23
const String kWebsiteReleasesUrl = 'https://docs.flutter.dev/development/tools/sdk/releases';
24

25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
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';

40 41 42 43
final RegExp releaseCandidateBranchRegex = RegExp(
  r'flutter-(\d+)\.(\d+)-candidate\.(\d+)',
);

44 45 46 47 48
/// Cast a dynamic to String and trim.
String stdoutToString(dynamic input) {
  final String str = input as String;
  return str.trim();
}
49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68

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;
}
69 70 71 72 73 74 75 76

/// 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.
77
String? getValueFromEnvOrArgs(
78 79
  String name,
  ArgResults argResults,
80
  Map<String, String> env, {
81 82
  bool allowNull = false,
}) {
83
  final String envName = fromArgToEnvName(name);
84
  if (env[envName] != null) {
85
    return env[envName];
86
  }
87
  final String? argValue = argResults[name] as String?;
88 89 90 91
  if (argValue != null) {
    return argValue;
  }

92 93 94
  if (allowNull) {
    return null;
  }
95
  throw ConductorException('Expected either the CLI arg --$name or the environment variable $envName '
96
      'to be provided!');
97 98
}

99 100 101 102 103 104 105
bool getBoolFromEnvOrArgs(
  String name,
  ArgResults argResults,
  Map<String, String> env,
) {
  final String envName = fromArgToEnvName(name);
  if (env[envName] != null) {
106
    return env[envName]?.toUpperCase() == 'TRUE';
107 108 109 110
  }
  return argResults[name] as bool;
}

111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126
/// 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] != '') {
127
    return env[envName]!.split(',');
128
  }
129
  final List<String>? argValues = argResults[name] as List<String>?;
130 131 132 133
  if (argValues != null) {
    return argValues;
  }

134
  throw ConductorException('Expected either the CLI arg --$name or the environment variable $envName '
135
      'to be provided!');
136 137 138 139 140 141 142 143
}

/// Translate CLI arg names to env variable names.
///
/// For example, 'state-file' -> 'STATE_FILE'.
String fromArgToEnvName(String argName) {
  return argName.toUpperCase().replaceAll(r'-', r'_');
}
144 145

/// Return a web link for the user to open a new PR.
146 147
///
/// Includes PR title and body via query params.
148 149 150
String getNewPrLink({
  required String userName,
  required String repoName,
151
  required pb.ConductorState state,
152
}) {
153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
  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';
    case 'engine':
      candidateBranch = state.engine.candidateBranch;
      workingBranch = state.engine.workingBranch;
      repoLabel = 'Engine';
    default:
168
      throw ConductorException('Expected repoName to be one of flutter or engine but got $repoName.');
169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
  }
  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
184
      // prefix with github org/repo so GitHub will auto-generate a hyperlink
185
      body.writeln('- Roll dart revision: dart-lang/sdk@${state.engine.dartRevision.substring(0, 9)}');
186
    }
187 188 189
    for (final pb.Cherrypick cp in state.engine.cherrypicks) {
      // Only list commits that map to a commit that exists upstream.
      if (cp.trunkRevision.isNotEmpty) {
190
        body.writeln('- commit: flutter/engine@${cp.trunkRevision.substring(0, 9)}');
191 192
      }
    }
193
  } else {
194 195 196 197 198 199
    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)}');
      }
    }
200
  }
201 202
  return 'https://github.com/flutter/$repoName/compare/'
      '$candidateBranch...$userName:$workingBranch?'
203 204 205
      'expand=1'
      '&title=${Uri.encodeQueryComponent(title)}'
      '&body=${Uri.encodeQueryComponent(body.toString())}';
206
}