blob: d117429dc6c5d0c2ffe9c7038b7c6fb4b944e2e5 [file] [log] [blame] [edit]
// Copyright (c) 2023, 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.
import 'dart:io';
import 'package:args/args.dart';
const _packageOption = 'package';
const _versionOption = 'version';
const _resetFlag = 'reset';
const _skipStableCheckFlag = 'skipStableCheck';
/// Note: Must be run from the /tool directory.
///
/// To prepare DWDS for release:
/// `dart run release.dart -p dwds`
///
/// To prepare WebDev for release:
/// `dart run release.dart -p webdev`
///
/// To reset DWDS after a release:
/// `dart run release.dart --reset -p dwds -v [[wip version]]`
///
/// To reset WebDev after a release:
/// `dart run release.dart --reset -p webdev -v [[wip version]]`
void main(List<String> arguments) async {
final parser = ArgParser()
..addOption(_packageOption, abbr: 'p', allowed: ['webdev', 'dwds'])
..addOption(_versionOption, abbr: 'v')
..addFlag(_resetFlag, abbr: 'r')
..addFlag(_skipStableCheckFlag, abbr: 's');
final argResults = parser.parse(arguments);
final package = argResults[_packageOption] as String?;
if (package == null) {
_logWarning('Please specify package with either --p=dwds or --p=webdev');
return;
}
final isReset = argResults[_resetFlag] as bool?;
final newVersion = argResults[_versionOption] as String?;
final skipStableCheck = argResults[_skipStableCheckFlag] as bool?;
int exitCode;
if (isReset == true) {
exitCode = await runReset(package: package, newVersion: newVersion);
} else {
exitCode = await runRelease(
package: package,
newVersion: newVersion,
skipStableCheck: skipStableCheck,
);
}
if (exitCode != 0) {
_logWarning('Run terminated unexpectedly with exit code: $exitCode');
}
}
Future<int> runReset({required String package, String? newVersion}) {
// Check that a new wip version has been provided.
final currentVersion = _readVersionFile(package);
if (newVersion == null || !newVersion.contains('wip')) {
_logInfo('''
Please provide the next wip version for $package, e.g. -v 3.0.1-wip
Current version is $currentVersion.
''');
return Future.value(1);
}
// Reset the dependency overrides for the package:
_updateOverrides(package, includeOverrides: true);
// Update the version strings in CHANGELOG and pubspec.yaml.
_updateVersionStrings(
package,
currentVersion: currentVersion,
nextVersion: newVersion,
isReset: true,
);
// Build the package.
final exitCode = _buildPackage(package);
return exitCode;
}
Future<int> runRelease({
required String package,
String? newVersion,
bool? skipStableCheck,
}) async {
// Check that we are on a stable version of Dart.
if (skipStableCheck != true) {
final checkVersionProcess = await Process.run('dart', ['--version']);
final versionInfo = checkVersionProcess.stdout as String;
if (!versionInfo.contains('stable')) {
_logWarning('''
Expected to be on stable version of Dart, instead on:
$versionInfo
To skip this check, re-run with --skipStableCheck
''');
return checkVersionProcess.exitCode;
}
}
// Update the pinned version of DWDS for webdev releases.
if (package == 'webdev') {
final newVersion = await _updateDwdsPin('webdev');
_logInfo('Add pinned DWDS info to CHANGELOG.');
final changelog = File('../webdev/CHANGELOG.md');
_addNewLine(
changelog,
newLine: '- Update `dwds` constraint to `${newVersion ?? 'TODO'}`.',
insertAt: 2,
);
}
// Remove any dependency overrides for the package:
_logInfo('Removing dependency overrides for $package.');
_updateOverrides(package, includeOverrides: false);
// Run dart pub upgrade.
for (final packagePath in [
'../dwds',
'../webdev',
'../frontend_server_common',
'../frontend_server_client',
'../test_common',
]) {
_logInfo('Upgrading pub packages for $packagePath');
final pubUpgradeProcess = await Process.run('dart', [
'pub',
'upgrade',
], workingDirectory: packagePath);
final upgradeErrors = pubUpgradeProcess.stderr as String;
if (upgradeErrors.isNotEmpty) {
_logWarning(upgradeErrors);
return pubUpgradeProcess.exitCode;
}
}
// Update the version strings in CHANGELOG and pubspec.yaml.
final currentVersion = _readVersionFile(package);
final nextVersion = newVersion ?? _removeWip(currentVersion);
_updateVersionStrings(
package,
currentVersion: currentVersion,
nextVersion: nextVersion,
);
// Build the package.
final exitCode = _buildPackage(package);
return exitCode;
}
Future<int> _buildPackage(String package) async {
_logInfo('Building $package');
final buildProcess = await Process.run('dart', [
'run',
'build_runner',
'build',
], workingDirectory: '../$package');
final buildErrors = buildProcess.stderr as String;
if (buildErrors.isNotEmpty) {
_logWarning(buildErrors);
}
return buildProcess.exitCode;
}
void _updateOverrides(String package, {required bool includeOverrides}) {
final overridesFilePath = '../$package/pubspec_overrides.yaml';
final noOverridesFilePath = '../$package/ignore_pubspec_overrides.yaml';
if (includeOverrides) {
_renameFile(currentName: noOverridesFilePath, newName: overridesFilePath);
} else {
_renameFile(currentName: overridesFilePath, newName: noOverridesFilePath);
}
}
void _renameFile({required String currentName, required String newName}) {
final currentFile = File(currentName);
if (!currentFile.existsSync()) {
_logInfo('Skip renaming $currentName to $newName, file does not exist.');
return;
}
currentFile.rename(newName);
}
void _updateVersionStrings(
String package, {
required String nextVersion,
required String currentVersion,
bool isReset = false,
}) {
_logInfo('Updating $package from $currentVersion to $nextVersion');
final pubspec = File('../$package/pubspec.yaml');
final changelog = File('../$package/CHANGELOG.md');
if (isReset) {
_addNewLine(changelog, newLine: '## $nextVersion');
_replaceInFile(pubspec, query: currentVersion, replaceWith: nextVersion);
} else {
for (final file in [pubspec, changelog]) {
_replaceInFile(file, query: currentVersion, replaceWith: nextVersion);
}
}
}
void _addNewLine(File file, {required String newLine, int insertAt = 0}) {
final currentLines = file.readAsLinesSync();
final linesBefore = currentLines.sublist(0, insertAt);
final linesAfter = currentLines.sublist(insertAt);
final newLines = [...linesBefore, newLine, '', ...linesAfter];
final content = newLines.joinWithNewLine();
return file.writeAsStringSync(content);
}
bool _replaceInFile(
File file, {
required String query,
required String replaceWith,
}) {
final newLines = <String>[];
var replaced = false;
for (final line in file.readAsLinesSync()) {
if (line.contains(query)) {
newLines.add(line.replaceAll(query, replaceWith));
replaced = true;
} else {
newLines.add(line);
}
}
final content = newLines.joinWithNewLine();
file.writeAsStringSync(content);
return replaced;
}
String _readVersionFile(String package) {
final versionFile = File('../$package/lib/src/version.dart');
final lines = versionFile.readAsLinesSync();
for (final line in lines) {
if (line.startsWith('const packageVersion =')) {
final version = line
.split('=')
.last
.split('')
.where((char) => char != ';' && char != "'" && char != '"')
.join('');
return version.trim();
}
}
throw Exception('Could not read version in $package/lib/src/version.dart');
}
String _removeWip(String wipVersion) {
if (!wipVersion.contains('wip')) {
throw Exception('$wipVersion is not a wip version.');
}
return wipVersion.split('-wip').first;
}
/// Returns the new pinned DWDS version on success.
Future<String?> _updateDwdsPin(String package) async {
final pubOutdatedProcess = await Process.run('dart', [
'pub',
'outdated',
'--no-dependency-overrides',
], workingDirectory: '../$package');
final lines = pubOutdatedProcess.stdout.split('\n') as List<String>;
String? nextDwdsVersion;
String? currentDwdsVersion;
for (final line in lines) {
if (line.trim().startsWith('dwds')) {
final segments = line
.trim()
.split(' ')
.where((segment) => segment != ' ');
nextDwdsVersion = segments.last;
currentDwdsVersion = segments
.lastWhere((segment) => segment.startsWith('*'))
.substring(1);
break;
}
}
final next = nextDwdsVersion ?? '';
final current = currentDwdsVersion ?? '';
if (next.isNotEmpty && current.isNotEmpty) {
_logInfo('Changing DWDS pin from $current to $next');
_replaceInFile(
File('../$package/pubspec.yaml'),
query: current,
replaceWith: next,
);
return nextDwdsVersion;
}
_logWarning('Unable to determine DWDS version to pin.');
return null;
}
void _logInfo(String message) {
stdout.writeln(message);
}
void _logWarning(String warning) {
stderr.writeln(warning);
}
extension JoinExtension on List<String> {
String joinWithNewLine() {
return '${join('\n')}\n';
}
}