blob: 639ae0370e61dc78f57d97f1ace903f00ab6d4c6 [file] [edit]
#!/usr/bin/env dart
// Copyright (c) 2026, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
// Rolls the browsers and JS engines pinned in DEPS (Chrome, Firefox, d8,
// jsshell and jsc) following go/dart-engprod/browsers.md.
//
// The tool checks for newer upstream releases, requests any missing CIPD
// packaging builds through the tool proxy, waits for the MPA requests to be
// approved and the packages to be built, uploads a DEPS roll CL, and waits
// for the CL to be approved and land.
//
// Each run posts to a single thread in the Dart Web team chat: the thread
// starts with the batched MPA approval links (or the roll CL if no builds
// were needed), and the roll CL, any failures and a final message when the
// CL lands are posted as replies. Use --dry-run to see what would happen
// without side effects.
import 'dart:io';
import 'package:args/args.dart';
import 'package:http/http.dart' as http;
import 'package:path/path.dart' as p;
import 'roll_browsers/engines.dart';
import 'roll_browsers/gerrit.dart';
import 'roll_browsers/git.dart';
import 'roll_browsers/notifier.dart';
import 'roll_browsers/roller.dart';
import 'roll_browsers/tool_proxy.dart';
import 'roll_browsers/upstream.dart';
const _defaultReason =
'b/334962955 -- Read go/dart-release-eng#reviewing-mpa-requests';
Future<void> main(List<String> arguments) async {
final parser = _buildParser();
final ArgResults args;
try {
args = parser.parse(arguments);
} on FormatException catch (e) {
_exitWithUsage(parser, e.message);
}
if (args.flag('help')) {
print(_usage(parser));
return;
}
exitCode = await _run(args);
}
ArgParser _buildParser() => ArgParser()
..addMultiOption(
'only',
help: 'Only consider these engines.',
allowed: [for (final engine in allEngines) engine.name],
)
..addFlag(
'dry-run',
abbr: 'n',
negatable: false,
help: 'Report what would happen without requesting builds, uploading '
'CLs or posting to chat.',
)
..addFlag(
'ignore-cadence',
negatable: false,
help: 'Roll to any newer version, ignoring the per-engine roll policy.',
)
..addMultiOption(
'reviewer',
abbr: 'r',
help: 'Reviewer email for uploaded roll CLs.',
)
..addOption(
'chat-space',
help: 'Google Chat space ID for the thread with MPA approval requests, '
'the roll CL and failures.',
defaultsTo: defaultChatSpace,
)
..addOption(
'reason',
help: 'Justification for tool proxy MPA requests.',
defaultsTo: _defaultReason,
)
..addFlag('help', abbr: 'h', negatable: false, help: 'Show this help.');
Future<int> _run(ArgResults args) async {
final checkoutRoot = _checkoutRoot();
final client = http.Client();
final notifier = Notifier(
chatSpace: args.option('chat-space')!,
dryRun: args.flag('dry-run'),
);
try {
final only = args.multiOption('only');
final roller = BrowserRoller(
checkout: GitCheckout(checkoutRoot),
upstream: Upstream(client),
toolProxy: ToolProxy(reason: args.option('reason')!),
gerrit: Gerrit(client),
notifier: notifier,
options: RollerOptions(
engines: [
for (final engine in allEngines)
if (only.isEmpty || only.contains(engine.name)) engine,
],
dryRun: args.flag('dry-run'),
ignoreCadence: args.flag('ignore-cadence'),
reviewers: args.multiOption('reviewer'),
),
);
return await roller.run() ? 0 : 1;
} catch (e, stackTrace) {
await notifier.reportFailure(
'unexpected error',
details: '$e\n\n$stackTrace',
);
return 1;
} finally {
// Post any failures that were waiting for the thread to start.
await notifier.flush();
client.close();
}
}
/// The SDK checkout containing this script.
String _checkoutRoot() {
final root = p.dirname(p.dirname(p.fromUri(Platform.script)));
if (!File(p.join(root, 'DEPS')).existsSync()) {
stderr.writeln('Could not find DEPS in $root.');
exit(1);
}
return root;
}
String _usage(ArgParser parser) => '''
Usage: dart tools/roll_browsers.dart [options]
Rolls the browsers and JS engines pinned in DEPS following
go/dart-engprod/browsers.md.
${parser.usage}''';
Never _exitWithUsage(ArgParser parser, String message) {
stderr.writeln(message);
stderr.writeln(_usage(parser));
exit(64);
}