Delete unused engine_hash.dart script (and test), simplify engine_hash.sh. (#160549)

Closes https://github.com/flutter/flutter/issues/160527.

We don't use the Dart script anyway, and the shell script could be
simplified to the single use in g3 (cl/688973229).
This commit is contained in:
Matan Lurey 2024-12-23 13:30:25 -08:00 committed by GitHub
parent 65ff060283
commit 62c6859e59
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 112 additions and 291 deletions

View File

@ -2579,6 +2579,7 @@ const Set<String> kExecutableAllowlist = <String>{
'dev/tools/repackage_gradle_wrapper.sh',
'dev/tools/bin/engine_hash.sh',
'dev/tools/format.sh',
'dev/tools/test/mock_git.sh',
'packages/flutter_tools/bin/macos_assemble.sh',
'packages/flutter_tools/bin/tool_backend.sh',

View File

@ -1,154 +0,0 @@
// 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.
// ---------------------------------- NOTE ----------------------------------
//
// We must keep the logic in this file consistent with the logic in the
// `engine_hash.sh` script in the same directory to ensure that Flutter
// continues to work across all platforms!
//
// --------------------------------------------------------------------------
import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'package:args/args.dart';
import 'package:crypto/crypto.dart';
enum GitRevisionStrategy { mergeBase, head }
final RegExp _hashRegex = RegExp(r'^([a-fA-F0-9]+)');
final ArgParser parser =
ArgParser()
..addOption(
'strategy',
abbr: 's',
allowed: <String>['head', 'mergeBase'],
defaultsTo: 'head',
allowedHelp: <String, String>{
'head': 'hash from git HEAD',
'mergeBase': 'hash from the merge-base of HEAD and upstream/master',
},
)
..addFlag('help', abbr: 'h', negatable: false);
Never printHelp({String? error}) {
final Stdout out = error != null ? stderr : stdout;
if (error != null) {
out.writeln(error);
out.writeln();
}
out.writeln('''
Calculate the hash signature for the Flutter Engine
${parser.usage}
''');
exit(error != null ? 1 : 0);
}
Future<int> main(List<String> args) async {
final ArgResults arguments;
try {
arguments = parser.parse(args);
} catch (e) {
printHelp(error: '$e');
}
if (arguments.wasParsed('help')) {
printHelp();
}
final String result;
try {
result = await engineHash(
(List<String> command) =>
Process.run(command.first, command.sublist(1), stdoutEncoding: utf8),
revisionStrategy: GitRevisionStrategy.values.byName(arguments.option('strategy')!),
);
} catch (e) {
stderr.writeln('Error calculating engine hash: $e');
return 1;
}
stdout.writeln(result);
return 0;
}
/// Returns the hash signature for the engine source code.
Future<String> engineHash(
Future<ProcessResult> Function(List<String> command) runProcess, {
GitRevisionStrategy revisionStrategy = GitRevisionStrategy.mergeBase,
}) async {
// First figure out the hash we're working with
final String base;
switch (revisionStrategy) {
case GitRevisionStrategy.head:
base = 'HEAD';
case GitRevisionStrategy.mergeBase:
final ProcessResult processResult = await runProcess(<String>[
'git',
'merge-base',
'upstream/master',
'HEAD',
]);
if (processResult.exitCode != 0) {
throw '''
Unable to find merge-base hash of the repository:
${processResult.stderr}''';
}
final Match? baseHash = _hashRegex.matchAsPrefix(processResult.stdout as String);
if (baseHash?.groupCount != 1) {
throw '''
Unable to parse merge-base hash of the repository
${processResult.stdout}''';
}
base = baseHash![1]!;
}
// List the tree (not the working tree) recursively for the merge-base.
// This is important for future filtering of files, but also do not include
// the developer's changes / in flight PRs.
// The presence `engine` and `DEPS` are signals that you live in a monorepo world.
final ProcessResult processResult = await runProcess(<String>[
'git',
'ls-tree',
'-r',
base,
'engine',
'DEPS',
]);
if (processResult.exitCode != 0) {
throw '''
Unable to list tree
${processResult.stderr}''';
}
// Ensure stable line endings so our hash calculation is stable
final String lsTree = processResult.stdout as String;
if (lsTree.trim().isEmpty) {
throw 'Not in a monorepo';
}
final Iterable<String> treeLines = LineSplitter.split(processResult.stdout as String);
// We could call `git hash-object --stdin` which would just take the input, calculate the size,
// and then sha1sum it like: `blob $size\0$string'. However, that can have different line endings.
// Instead this is equivalent to:
// git ls-tree -r $(git merge-base upstream/main HEAD) | <only newlines> | sha1sum
final StreamController<Digest> output = StreamController<Digest>();
final ByteConversionSink sink = sha1.startChunkedConversion(output);
for (final String line in treeLines) {
sink.add(utf8.encode(line));
sink.add(<int>[0x0a]);
}
sink.close();
final Digest digest = await output.stream.first;
return '$digest';
}

View File

@ -5,70 +5,65 @@
# ---------------------------------- NOTE ---------------------------------- #
#
# We must keep the logic in this file consistent with the logic in the
# `engine_hash.dart` script in the same directory to ensure that Flutter
# continues to work across all platforms!
# This file will appear unused within the monorepo. It is used internally
# (in google3) as part of the roll process, and care should be put before
# making changes.
#
# See cl/688973229.
#
# -------------------------------------------------------------------------- #
# TODO(codefu): Add a test that this always outputs the same hash as
# `engine_hash.dart` when the repositories are merged
# Needed because if it is set, cd may print the path it changed to.
unset CDPATH
STRATEGY=head
HELP=$(
cat <<EOF
Calculate the hash signature for the Flutter Engine\n
\t-s|--strategy\t<head,mergeBase>\n
\t\tthead: hash from git HEAD\n
\t\tmergeBase: hash from the merge-base of HEAD and upstream/master\n
EOF
# On Mac OS, readlink -f doesn't work, so follow_links traverses the path one
# link at a time, and then cds into the link destination and find out where it
# ends up.
#
# The returned filesystem path must be a format usable by Dart's URI parser,
# since the Dart command line tool treats its argument as a file URI, not a
# filename. For instance, multiple consecutive slashes should be reduced to a
# single slash, since double-slashes indicate a URI "authority", and these are
# supposed to be filenames. There is an edge case where this will return
# multiple slashes: when the input resolves to the root directory. However, if
# that were the case, we wouldn't be running this shell, so we don't do anything
# about it.
#
# The function is enclosed in a subshell to avoid changing the working directory
# of the caller.
function follow_links() (
cd -P "$(dirname -- "$1")"
file="$PWD/$(basename -- "$1")"
while [[ -h "$file" ]]; do
cd -P "$(dirname -- "$file")"
file="$(readlink -- "$file")"
cd -P "$(dirname -- "$file")"
file="$PWD/$(basename -- "$file")"
done
echo "$file"
)
function print_help() {
if [ "${1:-0}" -eq 0 ]; then
echo -e $HELP
exit 0
else
echo >&2 -e $HELP
exit $1
fi
}
PROG_NAME="$(follow_links "${BASH_SOURCE[0]}")"
BIN_DIR="$(cd "${PROG_NAME%/*}" ; pwd -P)"
FLUTTER_ROOT="$(cd "${BIN_DIR}/../../.." ; pwd -P)"
while [[ "$#" -gt 0 ]]; do
case $1 in
-s | --strategy)
STRATEGY="$2"
shift # past argument
shift # past value
;;
-h | --help)
print_help
;;
-* | --*)
echo >&2 -e "Unknown option $1\n"
print_help 1
;;
esac
done
BASE=HEAD
case $STRATEGY in
head) ;;
mergeBase)
BASE=$(git merge-base upstream/master HEAD)
;;
*)
echo >&2 -e "Unknown strategy $1\n"
print_help 1
;;
esac
LSTREE=$(git ls-tree -r $BASE engine DEPS)
if [ ${#LSTREE} -eq 0 ]; then
echo >&2 Error calculating engine hash: Not in a monorepo
exit 1
# Allow using a mock git for testing.
if [ -z "$GIT" ]; then
# By default, use git on PATH.
GIT_BIN="git"
else
HASH=$(echo "$LSTREE" | sha1sum | head -c 40)
echo $HASH
# Use the provide GIT executable.
GIT_BIN="$GIT"
fi
# Test for fusion repository
if [ -f "$FLUTTER_ROOT/DEPS" ]; then
ENGINE_VERSION=$($GIT_BIN -C "$FLUTTER_ROOT" merge-base HEAD origin/master)
elif [ -f "$FLUTTER_ROOT/bin/internal/engine.version" ]; then
ENGINE_VERSION=$(cat "$FLUTTER_ROOT/bin/internal/engine.version")
else
>&2 echo "Not a valid FLUTTER_ROOT: $FLUTTER_ROOT"
exit 1
fi
echo $ENGINE_VERSION

View File

@ -2,98 +2,71 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
@TestOn('posix')
library;
import 'dart:io' as io;
import 'package:collection/collection.dart';
import 'package:path/path.dart' as p;
import 'package:test/test.dart';
import '../bin/engine_hash.dart' show GitRevisionStrategy, engineHash;
/// Tests that `/dev/tools/bin/engine_hash.sh` _appears_ to work.
void main() {
test('Produces an engine hash for merge-base', () async {
final Future<io.ProcessResult> Function(List<String>) runProcess = _fakeProcesses(
processes: <FakeProcess>[
(
exe: 'git',
command: 'merge-base',
rest: <String>['upstream/master', 'HEAD'],
exitCode: 0,
stdout: 'abcdef1234',
stderr: null,
),
(
exe: 'git',
command: 'ls-tree',
rest: <String>['-r', 'abcdef1234', 'engine', 'DEPS'],
exitCode: 0,
stdout: 'one\r\ntwo\r\n',
stderr: null,
),
],
);
late final io.File engineHashSh;
final Future<String> result = engineHash(runProcess);
expect(result, completion('c708d7ef841f7e1748436b8ef5670d0b2de1a227'));
setUpAll(() {
engineHashSh = io.File(p.join(p.current, 'bin', 'engine_hash.sh'));
if (!engineHashSh.existsSync()) {
fail('No engine_hash.sh at "${p.absolute(engineHashSh.path)}".');
}
});
test('Produces an engine hash for HEAD', () async {
final Future<io.ProcessResult> Function(List<String>) runProcess = _fakeProcesses(
processes: <FakeProcess>[
(
exe: 'git',
command: 'ls-tree',
rest: <String>['-r', 'HEAD', 'engine', 'DEPS'],
exitCode: 0,
stdout: 'one\ntwo\n',
stderr: null,
),
],
);
late io.Directory tmpFlutterRoot;
final Future<String> result = engineHash(
runProcess,
revisionStrategy: GitRevisionStrategy.head,
);
setUp(() {
tmpFlutterRoot = io.Directory.systemTemp.createTempSync('engine_hash_test.');
expect(result, completion('c708d7ef841f7e1748436b8ef5670d0b2de1a227'));
// Create engine_hash.sh at the same component it would be in the real root.
io.Directory(p.join(tmpFlutterRoot.path, 'dev', 'tools', 'bin')).createSync(recursive: true);
engineHashSh.copySync(p.join(tmpFlutterRoot.path, 'dev', 'tools', 'bin', 'engine_hash.sh'));
// Create FLUTTER_ROOT/DEPS.
io.File(p.join(tmpFlutterRoot.path, 'DEPS')).createSync();
});
test('Returns error in non-monorepo', () async {
final Future<io.ProcessResult> Function(List<String>) runProcess = _fakeProcesses(
processes: <FakeProcess>[
(
exe: 'git',
command: 'ls-tree',
rest: <String>['-r', 'HEAD', 'engine', 'DEPS'],
exitCode: 0,
stdout: '',
stderr: null,
),
],
);
tearDown(() {
tmpFlutterRoot.deleteSync(recursive: true);
});
final Future<String> result = engineHash(
runProcess,
revisionStrategy: GitRevisionStrategy.head,
);
test('omission of FLUTTER_ROOT/DEPS falls back to engine.version', () {
io.File(p.join(tmpFlutterRoot.path, 'bin', 'internal', 'engine.version'))
..createSync(recursive: true)
..writeAsStringSync('12345');
io.File(p.join(tmpFlutterRoot.path, 'DEPS')).deleteSync();
expect(result, throwsA('Not in a monorepo'));
final io.ProcessResult result = io.Process.runSync(
p.join(tmpFlutterRoot.path, 'dev', 'tools', 'bin', 'engine_hash.sh'),
<String>[],
);
expect(result.exitCode, 0, reason: result.stderr.toString());
expect(result.stdout, '12345\n');
});
test('uses git -C merge-base HEAD origin/master', () {
final io.ProcessResult result = io.Process.runSync(
p.join(tmpFlutterRoot.path, 'dev', 'tools', 'bin', 'engine_hash.sh'),
<String>[],
environment: <String, String>{'GIT': p.join(p.current, 'test', 'mock_git.sh')},
);
expect(result.exitCode, 0, reason: result.stderr.toString());
expect(
result.stdout,
stringContainsInOrder(<String>[
'Mock Git: -C',
'engine_hash_test',
// This needs to be origin/master if the google3 script is running from a fresh checkout.
'merge-base HEAD origin/master',
]),
);
});
}
typedef FakeProcess =
({String exe, String command, List<String> rest, dynamic stdout, dynamic stderr, int exitCode});
Future<io.ProcessResult> Function(List<String>) _fakeProcesses({
required List<FakeProcess> processes,
}) => (List<String> cmd) async {
for (final FakeProcess process in processes) {
if (process.exe.endsWith(cmd[0]) &&
process.command.endsWith(cmd[1]) &&
process.rest.equals(cmd.sublist(2))) {
return io.ProcessResult(1, process.exitCode, process.stdout, process.stderr);
}
}
return io.ProcessResult(1, -42, '', '404 command not found: $cmd');
};

6
dev/tools/test/mock_git.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
# 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.
echo "Mock Git: $@"