From 29782f8750e847e82bde3aefb4970f4eba2e4cc3 Mon Sep 17 00:00:00 2001 From: Jens Johansen Date: Wed, 14 Feb 2024 13:56:18 +0000 Subject: [PATCH] [CFE] Add a few helper utils MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit These ease my life slightly. Examples: Compile to executable to get faster runs: ``` out/ReleaseX64/dart-sdk/bin/dart compile exe pkg/front_end/tool/gitformat_cl.dart -o ~/bin/format_cl out/ReleaseX64/dart-sdk/bin/dart compile exe pkg/front_end/tool/git_branch_helper.dart -o ~/bin/gitbranch ``` Now I can format my entire CL with ``` $ format_cl Using file:///path/to/dart-sdk/sdk/out/ReleaseX64/dart stdout> Formatted 3 files (0 changed) in 0.35 seconds. ``` and get info about my branching with ``` $ gitbranch ├── origin/main │ ├── add_a_few_cfe_util_tools [ahead 2] ``` Change-Id: I2e89196ab702660a1486894d22678e3cd0c07994 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/352641 Reviewed-by: Chloe Stefantsova Commit-Queue: Jens Johansen --- pkg/front_end/presubmit_helper.dart | 4 +- .../test/spell_checking_list_tests.txt | 3 + pkg/front_end/tool/format_cl.dart | 106 +++++++++++++ pkg/front_end/tool/git_branch_helper.dart | 144 ++++++++++++++++++ 4 files changed, 255 insertions(+), 2 deletions(-) create mode 100644 pkg/front_end/tool/format_cl.dart create mode 100644 pkg/front_end/tool/git_branch_helper.dart diff --git a/pkg/front_end/presubmit_helper.dart b/pkg/front_end/presubmit_helper.dart index 9cf984dd4ab..441afef8ae8 100644 --- a/pkg/front_end/presubmit_helper.dart +++ b/pkg/front_end/presubmit_helper.dart @@ -15,7 +15,7 @@ Future main(List args) async { // Expect something like /full/path/to/sdk/pkg/some_dir/whatever/else if (args.length != 1) throw "Need exactly one argument."; - final List changedFiles = _getChangedFiles(); + final List changedFiles = getChangedFiles(); String callerPath = args[0].replaceAll("\\", "/"); if (!_shouldRun(changedFiles, callerPath)) { return; @@ -290,7 +290,7 @@ Future _executePendingWorkItems(List workItems) async { /// Queries git about changes against upstream, or origin/main if no upstream is /// set. This is similar (but different), I believe, to what /// `git cl presubmit` does. -List _getChangedFiles() { +List getChangedFiles() { ProcessResult result = Process.runSync( "git", [ diff --git a/pkg/front_end/test/spell_checking_list_tests.txt b/pkg/front_end/test/spell_checking_list_tests.txt index 6c0af482383..b2adc068668 100644 --- a/pkg/front_end/test/spell_checking_list_tests.txt +++ b/pkg/front_end/test/spell_checking_list_tests.txt @@ -414,6 +414,7 @@ inconsistencies increasing incrementally increments +indention indents ing inhibit @@ -516,6 +517,7 @@ mxn mysdk naively naturally +needle negatable newworld ninja @@ -634,6 +636,7 @@ rediscover reducer reenable referential +refname refusing regards regenerate diff --git a/pkg/front_end/tool/format_cl.dart b/pkg/front_end/tool/format_cl.dart new file mode 100644 index 00000000000..f6622fafbc6 --- /dev/null +++ b/pkg/front_end/tool/format_cl.dart @@ -0,0 +1,106 @@ +// Copyright (c) 2024, 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:convert' show LineSplitter, utf8; +import 'dart:developer' as dev show NativeRuntime; +import 'dart:io'; + +import '../presubmit_helper.dart' show getChangedFiles; +import '../test/utils/io_utils.dart'; + +Future main() async { + Uri executable = getDartExecutable(); + final List allChangedFiles = getChangedFiles(); + if (allChangedFiles.isEmpty) { + print("No changes in CL."); + return; + } + final List changedDartFiles = []; + for (String changedFile in allChangedFiles) { + if (changedFile.toLowerCase().endsWith(".dart")) { + changedDartFiles.add(changedFile); + } + } + if (changedDartFiles.isEmpty) { + print("No changed dart files in CL."); + return; + } + Process p = await Process.start( + executable.toFilePath(), ["format", ...changedDartFiles]); + + p.stderr + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((String line) { + stderr.writeln("stderr> $line"); + }); + p.stdout + .transform(utf8.decoder) + .transform(const LineSplitter()) + .listen((String line) { + stdout.writeln("stdout> $line"); + }); + + exitCode = await p.exitCode; +} + +Uri getDartExecutable() { + Uri executable = Uri.base.resolve(Platform.resolvedExecutable); + if (executable == Platform.script || dev.NativeRuntime.buildId != null) { + // Probably aot compiled. We need to find "dart" another way. + bool found = false; + try { + final Uri repoDir = computeRepoDirUri(); + for (String candidatePath in [ + "out/ReleaseX64/dart", + "out/ReleaseX64/dart-sdk/bin/dart", + "out/ReleaseX64/dart.exe", + "out/ReleaseX64/dart-sdk/bin/dart.exe", + "xcodebuild/ReleaseX64/dart", + "xcodebuild/ReleaseX64/dart-sdk/bin/dart", + ]) { + Uri candidate = repoDir.resolve(candidatePath); + if (File.fromUri(candidate).existsSync()) { + executable = candidate; + found = true; + break; + } + } + } catch (e) { + print("Warning: $e"); + } + if (!found) { + Uri? candidate = where("dart"); + if (candidate != null) { + executable = candidate; + } else { + throw "Couldn't find a dart executable to use."; + } + } + print("Using $executable"); + } + return executable; +} + +Uri? where(String needle) { + String pathEnvironment = Platform.environment["PATH"] ?? ''; + List paths; + if (Platform.isWindows) { + paths = pathEnvironment.split(";"); + } else { + paths = pathEnvironment.split(":"); + } + // This isn't great but will probably work for our purposes. + List extensions = ["", ".exe", ".bat", ".com"]; + + for (String path in paths) { + for (String extension in extensions) { + File f = new File("$path${Platform.pathSeparator}$needle$extension"); + if (f.existsSync()) { + return f.uri; + } + } + } + return null; +} diff --git a/pkg/front_end/tool/git_branch_helper.dart b/pkg/front_end/tool/git_branch_helper.dart new file mode 100644 index 00000000000..1e27748df13 --- /dev/null +++ b/pkg/front_end/tool/git_branch_helper.dart @@ -0,0 +1,144 @@ +// Copyright (c) 2024, 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:async'; +import 'dart:io'; + +void main(List args) { + // Use `runZonedGuarded` instead of try/catch, and do it here before anything + // else has been printed --- both because of + // https://github.com/dart-lang/sdk/issues/54911. + runZonedGuarded(() { + mainImpl(args); + }, (e, _) { + stderr.writeln("Error: $e"); + }); +} + +const String CSI = "\x1b["; + +Map branches = {}; + +GitBranch getBranch(String name) { + GitBranch? result = branches[name]; + if (result == null) { + result = branches[name] = new GitBranch(name); + } + return result; +} + +void mainImpl(List args) { + ProcessResult result = Process.runSync("git", + ["branch", "--list", "--format=%(refname:short)%09%(upstream:short)"], + runInShell: true); + result.stdout.split("\n").forEach(processGitBranchLine); + + result = + Process.runSync("git", ["branch", "--show-current"], runInShell: true); + String currentBranchName = result.stdout; + currentBranchName = currentBranchName.trim(); + GitBranch currentBranch = branches[currentBranchName]!; + Set involvedBranchNames = {}; + currentBranch.collectSelfAndParentNames(involvedBranchNames); + currentBranch.collectSelfAndChildrenNames(involvedBranchNames); + + result = Process.runSync( + "git", + [ + "branch", + "--list", + "--format=%(refname:short)%09%(upstream:track)", + ...involvedBranchNames + ], + runInShell: true); + result.stdout.split("\n").forEach(processGitBranchTrackLine); + + int indentation = currentBranch.parent?.printSelfAndParentChain() ?? 0; + + currentBranch.printSelfAndChildren(indentation, color: true); +} + +void processGitBranchLine(String gitLine) { + if (gitLine.isEmpty) return; + int pos = gitLine.indexOf("\t"); + String thisName = gitLine.substring(0, pos); + String parentName = gitLine.substring(pos + 1).trim(); + GitBranch thisBranch = getBranch(thisName); + GitBranch parentBranch = getBranch(parentName); + parentBranch.registerChild(thisBranch); +} + +void processGitBranchTrackLine(String gitLine) { + if (gitLine.isEmpty) return; + int pos = gitLine.indexOf("\t"); + String thisName = gitLine.substring(0, pos); + String tracking = gitLine.substring(pos + 1).trim(); + GitBranch thisBranch = getBranch(thisName); + thisBranch.tracking = tracking; +} + +class GitBranch { + final String name; + GitBranch? parent; + final List children = []; + String? tracking; + + GitBranch(this.name); + + void collectSelfAndChildrenNames(Set names) { + names.add(name); + for (GitBranch child in children) { + child.collectSelfAndChildrenNames(names); + } + } + + void collectSelfAndParentNames(Set names) { + parent?.collectSelfAndParentNames(names); + names.add(name); + } + + void printSelfAndChildren(int indention, {bool color = false}) { + _printLineWithIndention(indention, color: color); + for (GitBranch child in children) { + child.printSelfAndChildren(indention + 1); + } + } + + int printSelfAndParentChain() { + int indention = 0; + GitBranch? parent = this.parent; + if (parent != null) { + indention = parent.printSelfAndParentChain(); + } + _printLineWithIndention(indention); + + return indention + 1; + } + + void registerChild(GitBranch child) { + children.add(child); + child.parent = this; + } + + @override + String toString() { + return "GitBranch[$name, children = $children]"; + } + + void _printLineWithIndention(int indention, {bool color = false}) { + stdout.write("│ " * (indention)); + stdout.write("├── "); + if (color) { + stdout.write("${CSI}31m"); + } + stdout.write("$name"); + if (color) { + stdout.write("${CSI}0m"); + } + if (tracking != null) { + stdout.write(" $tracking"); + } + stdout.write("\n"); + } +}