Rewrite update_flutter_sdk.sh as a Dart script (#6604)
diff --git a/.github/workflows/flutter-candidate-update.yaml b/.github/workflows/flutter-candidate-update.yaml index bb9f9ce..37de07e 100644 --- a/.github/workflows/flutter-candidate-update.yaml +++ b/.github/workflows/flutter-candidate-update.yaml
@@ -3,7 +3,7 @@ workflow_dispatch: # Allows for manual triggering if needed schedule: # * is a special character in YAML so you have to quote this string - - cron: "0 8 * * *" # Run every day at midnight Pacific Time + - cron: "0 8/12 * * *" # Run every day at midnight and noon Pacific Time permissions: contents: write
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cfa97a7..af768dd 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md
@@ -84,7 +84,7 @@ ### Workflow for making changes -1. Change your local Flutter SDK to the latest flutter candidate branch: `./tool/update_flutter_sdk.sh --local` +1. Change your local Flutter SDK to the latest flutter candidate branch: `devtools_tool update-flutter-sdk --local` 2. Create a branch from your cloned DevTools repo: `git checkout -b myBranch` 3. Ensure your branch, dependencies, and generated code are up-to-date: `devtools_tool sync` 4. Implement your changes, and commit to your branch: `git commit -m “description”`
diff --git a/tool/RELEASE_INSTRUCTIONS.md b/tool/RELEASE_INSTRUCTIONS.md index e4b08a7..3ab8fc0 100644 --- a/tool/RELEASE_INSTRUCTIONS.md +++ b/tool/RELEASE_INSTRUCTIONS.md
@@ -19,7 +19,7 @@ c. The local checkout is at `main` branch: `git rebase-update` 2. Your Flutter version is equal to latest candidate release branch: - - Run `./tool/update_flutter_sdk.sh --local` from the main devtools directory. + - Run `devtools_tool update-flutter-sdk --local` 3. You have goma [configured](http://go/ma-mac-setup). ### Prepare the release
diff --git a/tool/build_release.sh b/tool/build_release.sh index a46415a..c6113e1 100755 --- a/tool/build_release.sh +++ b/tool/build_release.sh
@@ -26,7 +26,7 @@ PATH="$FLUTTER_DIR/bin":$PATH # Make sure the flutter sdk is on the correct branch. - ./update_flutter_sdk.sh + devtools_tool update-flutter-sdk fi popd
diff --git a/tool/lib/commands/release_helper.dart b/tool/lib/commands/release_helper.dart index ba01e67..96542bf 100644 --- a/tool/lib/commands/release_helper.dart +++ b/tool/lib/commands/release_helper.dart
@@ -31,48 +31,20 @@ final useCurrentBranch = argResults!['use-current-branch']!; final currentBranchResult = await processManager.runProcess( - CliCommand.from( - 'git', - [ - 'rev-parse', - '--abbrev-ref', - 'HEAD', - ], - ), + CliCommand.git('rev-parse --abbrev-ref HEAD'), ); final initialBranch = currentBranchResult.stdout.trim(); String? releaseBranch; try { - // Change the CWD to the repo root Directory.current = pathFromRepoRoot(""); - print("Finding a remote that points to flutter/devtools.git."); - final devtoolsRemotesResult = await processManager.runProcess( - CliCommand.from( - 'git', - ['remote', '-v'], - ), + final remoteUpstream = await findRemote( + processManager, + remoteId: 'flutter/devtools.git', ); - final String devtoolsRemotes = devtoolsRemotesResult.stdout; - final remoteRegexp = RegExp( - r'^(?<remote>\S+)\s+(?<path>\S+)\s+\((?<action>\S+)\)', - multiLine: true, - ); - final remoteRegexpResults = remoteRegexp.allMatches(devtoolsRemotes); - final RegExpMatch devtoolsRemoteResult; - - try { - devtoolsRemoteResult = remoteRegexpResults.firstWhere( - (element) => RegExp(r'flutter/devtools.git$') - .hasMatch(element.namedGroup('path')!), - ); - } on StateError { - throw "ERROR: Couldn't find a remote that points to flutter/devtools.git. Instead got: \n$devtoolsRemotes"; - } - final remoteOrigin = devtoolsRemoteResult.namedGroup('remote')!; final gitStatusResult = await processManager.runProcess( - CliCommand.from('git', ['status', '-s']), + CliCommand.git('status -s'), ); final gitStatus = gitStatusResult.stdout; if (gitStatus.isNotEmpty) { @@ -85,17 +57,15 @@ if (!useCurrentBranch) { print("Preparing the release branch."); await processManager.runProcess( - CliCommand.from('git', ['fetch', remoteOrigin, 'master']), + CliCommand.git('fetch $remoteUpstream master'), ); } await processManager.runProcess( - CliCommand.from('git', [ - 'checkout', - '-b', - releaseBranch, - ...(useCurrentBranch ? [] : ['$remoteOrigin/master']), - ]), + CliCommand.git( + 'checkout -b $releaseBranch' + '${useCurrentBranch ? '' : ' $remoteUpstream/master'}', + ), ); print("Ensuring ./tool packages are ready."); @@ -131,18 +101,8 @@ await processManager.runAll( commands: [ - CliCommand.from('git', [ - 'commit', - '-a', - '-m', - commitMessage, - ]), - CliCommand.from('git', [ - 'push', - '-u', - remoteOrigin, - releaseBranch, - ]), + CliCommand.git('commit -a -m $commitMessage'), + CliCommand.git('push -u $remoteUpstream $releaseBranch'), ], );
diff --git a/tool/lib/commands/repo_check.dart b/tool/lib/commands/repo_check.dart index b22c325..91f7a6c 100644 --- a/tool/lib/commands/repo_check.dart +++ b/tool/lib/commands/repo_check.dart
@@ -61,14 +61,15 @@ Future<void> performCheck(DevToolsRepo repo) { // TODO(devoncarew): Update this to use a package to parse the pubspec file; // https://pub.dev/packages/pubspec. - final pubspecContents = repo.readFile('packages/devtools_app/pubspec.yaml'); + final pubspecContents = + repo.readFile(Uri.parse('packages/devtools_app/pubspec.yaml')); final versionString = pubspecContents .split('\n') .firstWhere((line) => line.startsWith('version:')); final pubspecVersion = versionString.substring('version:'.length).trim(); final dartFileContents = - repo.readFile('packages/devtools_app/lib/devtools.dart'); + repo.readFile(Uri.parse('packages/devtools_app/lib/devtools.dart')); final regexp = RegExp(r"version = '(\S+)';"); final match = regexp.firstMatch(dartFileContents);
diff --git a/tool/lib/commands/update_dart_sdk_deps.dart b/tool/lib/commands/update_dart_sdk_deps.dart index 296ae1b..2dfeaa8 100644 --- a/tool/lib/commands/update_dart_sdk_deps.dart +++ b/tool/lib/commands/update_dart_sdk_deps.dart
@@ -19,7 +19,7 @@ /// automatically built and uploaded to CIPD on each DevTools commit. /// /// To run this script: -/// `dart run tool/bin/devtools_tool.dart update-sdk-deps -c <commit-hash>` +/// `devtools_tool update-sdk-deps -c <commit-hash>` class UpdateDartSdkDepsCommand extends Command { UpdateDartSdkDepsCommand() { argParser.addOption( @@ -49,11 +49,11 @@ workingDirectory: dartSdkLocation, additionalErrorMessage: DartSdkHelper.commandDebugMessage, commands: [ - CliCommand( - 'git branch -D devtools-$commit', + CliCommand.git( + 'branch -D devtools-$commit', throwOnException: false, ), - CliCommand('git new-branch devtools-$commit'), + CliCommand.git('new-branch devtools-$commit'), ], ); @@ -65,7 +65,7 @@ workingDirectory: dartSdkLocation, additionalErrorMessage: DartSdkHelper.commandDebugMessage, commands: [ - CliCommand('git add .'), + CliCommand.git('add .'), CliCommand.from( 'git', [ @@ -74,7 +74,7 @@ 'Update DevTools rev to $commit', ], ), - CliCommand('git cl upload -s -f'), + CliCommand.git('cl upload -s -f'), ], ); }
diff --git a/tool/lib/commands/update_flutter_sdk.dart b/tool/lib/commands/update_flutter_sdk.dart new file mode 100644 index 0000000..d0eb5c2 --- /dev/null +++ b/tool/lib/commands/update_flutter_sdk.dart
@@ -0,0 +1,167 @@ +// Copyright 2023 The Chromium 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 'dart:io'; + +import 'package:args/command_runner.dart'; +import 'package:cli_util/cli_logging.dart'; +import 'package:devtools_tool/model.dart'; +import 'package:io/io.dart'; +import 'package:path/path.dart' as path; + +import '../utils.dart'; + +const _localFlag = 'local'; +const _useCacheFlag = 'use-cache'; + +/// This command updates the the Flutter SDK contained in the 'tool/' directory +/// to the latest Flutter candidate branch. +/// +/// When the '--local' flag is passed, your local flutter/flutter checkout will +/// be updated as well. +/// +/// This command will use the Flutter version from the 'flutter-candidate.txt' +/// file in the repository root, unless the '--no-use-cache' flag is passed, +/// in which case it will run the 'tool/latest_flutter_candidate.sh' script to +/// fetch the latest version from upstream. +/// +/// The version from 'flutter-candidate.txt' should be identical most of the +/// time since the GitHub workflow that updates this file runs twice per day. +/// +/// To run this script: +/// `devtools_tool update-flutter-sdk [--local] [--no-use-cache]` +class UpdateFlutterSdkCommand extends Command { + UpdateFlutterSdkCommand() { + argParser + ..addFlag( + _localFlag, + negatable: false, + help: 'Update your local checkout of the Flutter SDK', + ) + ..addFlag( + _useCacheFlag, + negatable: true, + defaultsTo: true, + help: + 'Use the cached Flutter version stored in "flutter-candidate.txt" ' + 'instead of the latest version at ' + '"https://flutter.googlesource.com/mirrors/flutter/"', + ); + } + @override + String get name => 'update-flutter-sdk'; + + @override + String get description => + 'Updates the "devtools_rev" hash in the Dart SDK DEPS file with the ' + 'provided commit hash, and creates a Gerrit CL for review'; + + @override + Future run() async { + final updateLocalFlutter = argResults![_localFlag]; + final useCachedVersion = argResults![_useCacheFlag]; + final log = Logger.standard(); + + // TODO(kenz): we can remove this if we can rewrite the + // 'latest_flutter_candidate.sh' script as a Dart script, or if we instead + // duplicate it as a Windows script (we may need this to be a non-Dart + // script for execution on the bots before we have a Dart SDK available). + if (Platform.isWindows && !useCachedVersion) { + log.stderr( + 'On windows, you can only use the cached Flutter version from ' + '"flutter-candidate.txt". Please remove the "--no-use-cache" flag and ' + 'try again.', + ); + return 1; + } + + final repo = DevToolsRepo.getInstance(); + final processManager = ProcessManager(); + + late String flutterTag; + if (useCachedVersion) { + flutterTag = + 'tags/${repo.readFile(Uri.parse('flutter-candidate.txt')).trim()}'; + } else { + flutterTag = (await processManager.runProcess( + CliCommand('sh latest_flutter_candidate.sh'), + workingDirectory: repo.toolDirectoryPath, + )) + .stdout + .replaceFirst('refs/', ''); + } + + log.stdout( + 'Updating to Flutter version ' + '${useCachedVersion ? 'from cache' : 'from upstream'}: $flutterTag ', + ); + + if (updateLocalFlutter) { + final sdk = FlutterSdk.getSdk(); + if (sdk == null) { + print('Unable to locate a Flutter sdk.'); + return 1; + } + + log.stdout('Updating local Flutter checkout...'); + + // Verify we have an upstream remote to pull from. + await findRemote( + processManager, + remoteId: 'flutter/flutter.git', + workingDirectory: sdk.sdkPath, + ); + + await processManager.runAll( + commands: [ + CliCommand.git('stash'), + CliCommand.git('fetch upstream'), + CliCommand.git('checkout upstream/master'), + CliCommand.git('reset --hard upstream/master'), + CliCommand.git('checkout $flutterTag -f'), + CliCommand.flutter('--version'), + ], + workingDirectory: sdk.sdkPath, + ); + log.stdout('Finished updating local Flutter checkout.'); + } + + final flutterSdkDirName = 'flutter-sdk'; + final toolSdkPath = path.join( + repo.toolDirectoryPath, + flutterSdkDirName, + ); + final toolFlutterSdk = Directory.fromUri(Uri.parse(toolSdkPath)); + log.stdout('Updating "$toolSdkPath" to branch $flutterTag'); + + if (toolFlutterSdk.existsSync()) { + log.stdout('"$toolSdkPath" directory already exists'); + await processManager.runAll( + commands: [ + CliCommand.git('fetch'), + CliCommand.git('checkout $flutterTag -f'), + CliCommand('./bin/flutter --version'), + ], + workingDirectory: toolFlutterSdk.path, + ); + } else { + log.stdout('"$toolSdkPath" directory does not exist - cloning it now'); + await processManager.runProcess( + CliCommand.git( + 'clone https://github.com/flutter/flutter $flutterSdkDirName', + ), + workingDirectory: repo.toolDirectoryPath, + ); + await processManager.runAll( + commands: [ + CliCommand.git('checkout $flutterTag -f'), + CliCommand('./bin/flutter --version'), + ], + workingDirectory: toolFlutterSdk.path, + ); + } + + log.stdout('Finished updating $toolSdkPath.'); + } +}
diff --git a/tool/lib/devtools_command_runner.dart b/tool/lib/devtools_command_runner.dart index 371ee2a..29afd0e 100644 --- a/tool/lib/devtools_command_runner.dart +++ b/tool/lib/devtools_command_runner.dart
@@ -7,6 +7,7 @@ import 'package:devtools_tool/commands/fix_goldens.dart'; import 'package:devtools_tool/commands/generate_code.dart'; import 'package:devtools_tool/commands/sync.dart'; +import 'package:devtools_tool/commands/update_flutter_sdk.dart'; import 'package:io/io.dart'; import 'commands/analyze.dart'; @@ -22,16 +23,17 @@ DevToolsCommandRunner() : super('devtools_tool', 'A repo management tool for DevTools.') { addCommand(AnalyzeCommand()); - addCommand(RepoCheckCommand()); - addCommand(ListCommand()); - addCommand(PubGetCommand()); - addCommand(RollbackCommand()); - addCommand(UpdateDartSdkDepsCommand()); - addCommand(ReleaseHelperCommand()); - addCommand(UpdateDevToolsVersionCommand()); addCommand(FixGoldensCommand()); addCommand(GenerateCodeCommand()); + addCommand(ListCommand()); + addCommand(PubGetCommand()); + addCommand(ReleaseHelperCommand()); + addCommand(RepoCheckCommand()); + addCommand(RollbackCommand()); addCommand(SyncCommand()); + addCommand(UpdateDartSdkDepsCommand()); + addCommand(UpdateDevToolsVersionCommand()); + addCommand(UpdateFlutterSdkCommand()); } @override
diff --git a/tool/lib/model.dart b/tool/lib/model.dart index 5fb46f5..9eed7fa 100644 --- a/tool/lib/model.dart +++ b/tool/lib/model.dart
@@ -9,8 +9,12 @@ class DevToolsRepo { DevToolsRepo._create(this.repoPath); + /// The path to the DevTools repository root. final String repoPath; + /// The path to the DevTools 'tool' directory. + String get toolDirectoryPath => path.join(repoPath, 'tool'); + @override String toString() => '[DevTools $repoPath]'; @@ -80,8 +84,9 @@ } } - String readFile(String filePath) { - return File(path.join(repoPath, filePath)).readAsStringSync(); + /// Reads the file at [uri], which should be a relative path from [repoPath]. + String readFile(Uri uri) { + return File(path.join(repoPath, uri.path)).readAsStringSync(); } }
diff --git a/tool/lib/utils.dart b/tool/lib/utils.dart index 4928d92..6157dc3 100644 --- a/tool/lib/utils.dart +++ b/tool/lib/utils.dart
@@ -22,9 +22,9 @@ workingDirectory: dartSdkLocation, additionalErrorMessage: commandDebugMessage, commands: [ - CliCommand('git fetch origin'), - CliCommand('git rebase-update'), - CliCommand('git checkout origin/main'), + CliCommand.git('fetch origin'), + CliCommand.git('rebase-update'), + CliCommand.git('checkout origin/main'), ], ); } @@ -84,6 +84,17 @@ ); } + factory CliCommand.git( + String args, { + bool throwOnException = true, + }) { + return CliCommand._( + exe: 'git', + args: args.split(' '), + throwOnException: throwOnException, + ); + } + factory CliCommand.tool( String args, { bool throwOnException = true, @@ -98,6 +109,11 @@ late final String exe; late final List<String> args; final bool throwOnException; + + @override + String toString() { + return [exe, ...args].join(' '); + } } typedef DevToolsProcessResult = ({int exitCode, String stdout, String stderr}); @@ -152,3 +168,46 @@ String pathFromRepoRoot(String pathFromRoot) { return path.join(DevToolsRepo.getInstance().repoPath, pathFromRoot); } + +/// Returns the name of the git remote with id [remoteId] in +/// [workingDirectory]. +/// +/// When [workingDirectory] is null, this method will look for the remote in +/// the current directory. +/// +/// [remoteId] should have the form <organization>/<repository>.git. For +/// example: 'flutter/flutter.git' or 'flutter/devtools.git'. +Future<String> findRemote( + ProcessManager processManager, { + required String remoteId, + String? workingDirectory, +}) async { + print('Searching for a remote that points to $remoteId.'); + final remotesResult = await processManager.runProcess( + CliCommand.git('remote -v'), + workingDirectory: workingDirectory, + ); + final String remotes = remotesResult.stdout; + final remoteRegexp = RegExp( + r'^(?<remote>\S+)\s+(?<path>\S+)\s+\((?<action>\S+)\)', + multiLine: true, + ); + final remoteRegexpResults = remoteRegexp.allMatches(remotes); + final RegExpMatch upstreamRemoteResult; + + try { + upstreamRemoteResult = remoteRegexpResults.firstWhere( + // ignore: prefer_interpolation_to_compose_strings + (element) => RegExp(r'' + remoteId + '\$') + .hasMatch(element.namedGroup('path')!), + ); + } on StateError { + throw StateError( + "Couldn't find a remote that points to flutter/devtools.git. " + "Instead got: \n$remotes", + ); + } + final remoteUpstream = upstreamRemoteResult.namedGroup('remote')!; + print('Found upstream remote.'); + return remoteUpstream; +}
diff --git a/tool/update_flutter_sdk.sh b/tool/update_flutter_sdk.sh deleted file mode 100755 index b4562f8..0000000 --- a/tool/update_flutter_sdk.sh +++ /dev/null
@@ -1,76 +0,0 @@ -#!/bin/bash - -# Copyright 2021 The Chromium Authors. All rights reserved. -# Use of this source code is governed by a BSD-style license that can be -# found in the LICENSE file. - -# Any subsequent commands failure will cause this script to exit immediately -set -e - -UPDATE_LOCALLY=$1 - -# Contains a path to this script, relative to the directory it was called from. -RELATIVE_PATH_TO_SCRIPT="${BASH_SOURCE[0]}" - -# The directory that this script is located in. -TOOL_DIR=`dirname "${RELATIVE_PATH_TO_SCRIPT}"` - -pushd "$TOOL_DIR" -dart pub get -REQUIRED_FLUTTER_TAG="$(./latest_flutter_candidate.sh | sed 's/^.*refs\///')" - -echo "REQUIRED_FLUTTER_TAG: $REQUIRED_FLUTTER_TAG" - -if [[ $UPDATE_LOCALLY = "--local" ]]; then - echo "STATUS: Updating local Flutter checkout to branch '$REQUIRED_FLUTTER_TAG'." - - FLUTTER_EXE=`which flutter` - FLUTTER_BIN=`dirname "${FLUTTER_EXE}"` - FLUTTER_DIR="$FLUTTER_BIN/.." - - pushd $FLUTTER_DIR - - UPSTREAM_REMOTE_COUNT=$(git remote -v| grep -cE '^upstream[[:space:]]+git@github.com:flutter/flutter.git' || true) - if [ "$UPSTREAM_REMOTE_COUNT" -lt "2" ] ; then - echo "Error: please make sure the flutter repository 'upstream' remote is set to 'git@github.com:flutter/flutter.git'"; - exit 1; - fi - # Stash any local flutter SDK changes if they exist. - git stash - git fetch upstream - git checkout upstream/master - git reset --hard upstream/master - git checkout $REQUIRED_FLUTTER_TAG -f - flutter --version - popd - - echo "STATUS: Finished updating local Flutter checkout." -fi - -FLUTTER_DIR="flutter-sdk" -PATH="$FLUTTER_DIR/bin":$PATH - -echo "STATUS: Updating 'tool/flutter-sdk' to branch '$REQUIRED_FLUTTER_TAG'." - -if [ -d "$FLUTTER_DIR" ]; then - echo "STATUS: 'tool/$FLUTTER_DIR' directory already exists" - - # switch to the specified version - pushd $FLUTTER_DIR - git fetch - git checkout $REQUIRED_FLUTTER_TAG -f - ./bin/flutter --version - popd -else - echo "STATUS: 'tool/$FLUTTER_DIR' directory does not exist - cloning it now" - - # clone the flutter repo and switch to the specified version - git clone https://github.com/flutter/flutter "$FLUTTER_DIR" - pushd "$FLUTTER_DIR" - git checkout $REQUIRED_FLUTTER_TAG - ./bin/flutter --version - popd -fi - -popd -echo "STATUS: Finished updating 'tool/flutter-sdk'."