Skip to content

Commit a401c5d

Browse files
authored
Add bin/internal/last_engine_commit.sh and tests. (flutter#168387)
Towards flutter#168273. Once merged, the recipes branch (specifically `release_packager.py`) can use this as a lint. /cc @reidbaker
1 parent 397d3e1 commit a401c5d

File tree

5 files changed

+331
-3
lines changed

5 files changed

+331
-3
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Copyright 2014 The Flutter Authors. All rights reserved.
2+
# Use of this source code is governed by a BSD-style license that can be
3+
# found in the LICENSE file.
4+
5+
# Based on the current repository state, writes on stdout the last commit in the
6+
# git tree that edited either `DEPS` or any file in the `engine/` sub-folder,
7+
# which is used to ensure `bin/internal/engine.version` is set correctly.
8+
9+
# ---------------------------------- NOTE ---------------------------------- #
10+
#
11+
# Please keep the logic in this file consistent with the logic in the
12+
# `last_engine_commit.sh` script in the same directory to ensure that Flutter
13+
# continues to work across all platforms!
14+
#
15+
# https://github.com/flutter/flutter/blob/main/docs/tool/Engine-artifacts.md.
16+
#
17+
# Want to test this script?
18+
# $ cd dev/tools
19+
# $ dart test test/last_engine_commit_test.dart
20+
#
21+
# -------------------------------------------------------------------------- #
22+
23+
$ErrorActionPreference = "Stop"
24+
25+
$progName = Split-Path -parent $MyInvocation.MyCommand.Definition
26+
$flutterRoot = (Get-Item $progName).parent.parent.FullName
27+
28+
cmd /c "git log -1 --pretty=format:%H -- ""$(git rev-parse --show-toplevel)/DEPS"" ""$(git rev-parse --show-toplevel)/engine"""

bin/internal/last_engine_commit.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/usr/bin/env bash
2+
# Copyright 2014 The Flutter Authors. All rights reserved.
3+
# Use of this source code is governed by a BSD-style license that can be
4+
# found in the LICENSE file.
5+
6+
# Based on the current repository state, writes on stdout the last commit in the
7+
# git tree that edited either `DEPS` or any file in the `engine/` sub-folder,
8+
# which is used to ensure `bin/internal/engine.version` is set correctly.
9+
#
10+
11+
# ---------------------------------- NOTE ---------------------------------- #
12+
#
13+
# Please keep the logic in this file consistent with the logic in the
14+
# `last_engine_commit.ps1` script in the same directory to ensure that Flutter
15+
# continues to work across all platforms!
16+
#
17+
# https://github.com/flutter/flutter/blob/main/docs/tool/Engine-artifacts.md.
18+
#
19+
# Want to test this script?
20+
# $ cd dev/tools
21+
# $ dart test test/last_engine_commit_test.dart
22+
#
23+
# -------------------------------------------------------------------------- #
24+
25+
set -e
26+
27+
FLUTTER_ROOT="$(dirname "$(dirname "$(dirname "${BASH_SOURCE[0]}")")")"
28+
29+
git log -1 --pretty=format:%H -- "$(git rev-parse --show-toplevel)/DEPS" "$(git rev-parse --show-toplevel)/engine"

dev/bots/analyze.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2660,6 +2660,7 @@ const Set<String> kExecutableAllowlist = <String>{
26602660
'bin/dart',
26612661
'bin/flutter',
26622662
'bin/flutter-dev',
2663+
'bin/internal/last_engine_commit.sh',
26632664
'bin/internal/update_dart_sdk.sh',
26642665
'bin/internal/update_engine_version.sh',
26652666
'bin/internal/content_aware_hash.sh',
Lines changed: 259 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,259 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
@TestOn('vm')
6+
library;
7+
8+
import 'dart:io' as io;
9+
10+
import 'package:file/file.dart';
11+
import 'package:file/local.dart';
12+
import 'package:platform/platform.dart';
13+
import 'package:test/test.dart';
14+
15+
//////////////////////////////////////////////////////////////////////
16+
// //
17+
// ✨ THINKING OF MOVING/REFACTORING THIS FILE? READ ME FIRST! ✨ //
18+
// //
19+
// There is a link to this file in //docs/tool/Engine-artfiacts.md //
20+
// and it would be very kind of you to update the link, if needed. //
21+
// //
22+
//////////////////////////////////////////////////////////////////////
23+
24+
void main() {
25+
// Want to test the powershell (update_engine_version.ps1) file, but running
26+
// a macOS or Linux machine? You can install powershell and then opt-in to
27+
// running `pwsh bin/internal/update_engine_version.ps1`.
28+
//
29+
// macOS: https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-macos
30+
// linux: https://learn.microsoft.com/en-us/powershell/scripting/install/installing-powershell-on-linux
31+
//
32+
// Then, set this variable to true:
33+
final bool usePowershellOnPosix = () {
34+
// Intentionally not a const so that linting doesn't go wild across the test.
35+
return false;
36+
}();
37+
38+
const FileSystem localFs = LocalFileSystem();
39+
final _FlutterRootUnderTest flutterRoot = _FlutterRootUnderTest.findWithin(
40+
forcePowershell: usePowershellOnPosix,
41+
);
42+
43+
late Directory tmpDir;
44+
late _FlutterRootUnderTest testRoot;
45+
late Map<String, String> environment;
46+
47+
void printIfNotEmpty(String prefix, String string) {
48+
if (string.isNotEmpty) {
49+
string.split(io.Platform.lineTerminator).forEach((String s) {
50+
print('$prefix:>$s<');
51+
});
52+
}
53+
}
54+
55+
io.ProcessResult run(String executable, List<String> args) {
56+
print('Running "$executable ${args.join(" ")}');
57+
final io.ProcessResult result = io.Process.runSync(
58+
executable,
59+
args,
60+
environment: environment,
61+
workingDirectory: testRoot.root.absolute.path,
62+
includeParentEnvironment: false,
63+
);
64+
if (result.exitCode != 0) {
65+
fail(
66+
'Failed running "$executable $args" (exit code = ${result.exitCode}),'
67+
'\nstdout: ${result.stdout}'
68+
'\nstderr: ${result.stderr}',
69+
);
70+
}
71+
printIfNotEmpty('stdout', (result.stdout as String).trim());
72+
printIfNotEmpty('stderr', (result.stderr as String).trim());
73+
return result;
74+
}
75+
76+
setUpAll(() async {
77+
if (usePowershellOnPosix) {
78+
final io.ProcessResult result = io.Process.runSync('pwsh', <String>['--version']);
79+
print('Using Powershell (${result.stdout}) on POSIX for local debugging and testing');
80+
}
81+
});
82+
83+
/// Initializes a blank git repo in [testRoot.root].
84+
void initGitRepoWithBlankInitialCommit() {
85+
run('git', <String>['init', '--initial-branch', 'master']);
86+
run('git', <String>['config', '--local', 'user.email', '[email protected]']);
87+
run('git', <String>['config', '--local', 'user.name', 'Test User']);
88+
run('git', <String>['add', '.']);
89+
run('git', <String>['commit', '--allow-empty', '-m', 'Initial commit']);
90+
}
91+
92+
late int commitCount;
93+
94+
setUp(() async {
95+
commitCount = 0;
96+
97+
tmpDir = localFs.systemTempDirectory.createTempSync('last_engine_commit_test.');
98+
testRoot = _FlutterRootUnderTest.fromPath(
99+
tmpDir.childDirectory('flutter').path,
100+
forcePowershell: usePowershellOnPosix,
101+
);
102+
103+
environment = <String, String>{};
104+
105+
if (const LocalPlatform().isWindows || usePowershellOnPosix) {
106+
// Copy a minimal set of environment variables needed to run the update_engine_version script in PowerShell.
107+
const List<String> powerShellVariables = <String>['SystemRoot', 'Path', 'PATHEXT'];
108+
for (final String key in powerShellVariables) {
109+
final String? value = io.Platform.environment[key];
110+
if (value != null) {
111+
environment[key] = value;
112+
}
113+
}
114+
}
115+
116+
// Copy the update_engine_version script and create a rough directory structure.
117+
flutterRoot.binInternalLastEngineCommit.copySyncRecursive(
118+
testRoot.binInternalLastEngineCommit.path,
119+
);
120+
121+
initGitRepoWithBlankInitialCommit();
122+
});
123+
124+
tearDown(() {
125+
tmpDir.deleteSync(recursive: true);
126+
});
127+
128+
/// Runs `bin/internal/last_engine_commit.{sh|ps1}` and returns the stdout.
129+
///
130+
/// - On Windows, `powershell` is used (to run `last_engine_commit.ps1`);
131+
/// - On POSIX, if [usePowershellOnPosix] is set, `pwsh` is used (to run `last_engine_commit.ps1`);
132+
/// - Otherwise, `last_engine_commit.sh` is used.
133+
String getLastEngineCommit() {
134+
final String executable;
135+
final List<String> args;
136+
if (const LocalPlatform().isWindows) {
137+
executable = 'powershell';
138+
args = <String>[testRoot.binInternalLastEngineCommit.path];
139+
} else if (usePowershellOnPosix) {
140+
executable = 'pwsh';
141+
args = <String>[testRoot.binInternalLastEngineCommit.path];
142+
} else {
143+
executable = testRoot.binInternalLastEngineCommit.path;
144+
args = <String>[];
145+
}
146+
return run(executable, args).stdout as String;
147+
}
148+
149+
void writeCommit(Iterable<String> files) {
150+
commitCount++;
151+
for (final String relativePath in files) {
152+
localFs.file(localFs.path.join(testRoot.root.path, relativePath))
153+
..createSync(recursive: true)
154+
..writeAsStringSync('$commitCount');
155+
}
156+
run('git', <String>['add', '.']);
157+
run('git', <String>['commit', '-m', 'Wrote ${files.length} files']);
158+
}
159+
160+
test('returns the last engine commit', () {
161+
writeCommit(<String>['DEPS', 'engine/README.md']);
162+
163+
final String lastEngine = getLastEngineCommit();
164+
expect(lastEngine, isNotEmpty);
165+
166+
writeCommit(<String>['CHANGELOG.md', 'dev/folder/called/engine/README.md']);
167+
expect(getLastEngineCommit(), lastEngine);
168+
});
169+
170+
test('considers DEPS an engine change', () {
171+
writeCommit(<String>['DEPS', 'engine/README.md']);
172+
173+
final String lastEngineA = getLastEngineCommit();
174+
expect(lastEngineA, isNotEmpty);
175+
176+
writeCommit(<String>['DEPS']);
177+
final String lastEngineB = getLastEngineCommit();
178+
expect(lastEngineB, allOf(isNotEmpty, isNot(equals(lastEngineA))));
179+
});
180+
}
181+
182+
extension on File {
183+
void copySyncRecursive(String newPath) {
184+
fileSystem.directory(fileSystem.path.dirname(newPath)).createSync(recursive: true);
185+
copySync(newPath);
186+
}
187+
}
188+
189+
/// A FrUT, or "Flutter Root"-Under Test (parallel to a SUT, System Under Test).
190+
///
191+
/// For the intent of this test case, the "Flutter Root" is a directory
192+
/// structure with the following elements:
193+
///
194+
/// ```txt
195+
/// ├── DEPS
196+
/// ├── engine/
197+
/// ├── bin/
198+
/// │ ├── internal/
199+
/// │ │ └── last_engine_commit.{sh|ps1}
200+
/// ```
201+
final class _FlutterRootUnderTest {
202+
/// Creates a root-under test using [path] as the root directory.
203+
///
204+
/// It is assumed the files already exist or will be created if needed.
205+
factory _FlutterRootUnderTest.fromPath(
206+
String path, {
207+
FileSystem fileSystem = const LocalFileSystem(),
208+
Platform platform = const LocalPlatform(),
209+
bool forcePowershell = false,
210+
}) {
211+
final Directory root = fileSystem.directory(path);
212+
return _FlutterRootUnderTest._(
213+
root,
214+
depsFile: root.childFile('DEPS'),
215+
engineDirectory: root.childDirectory('engine'),
216+
binInternalLastEngineCommit: root.childFile(
217+
fileSystem.path.join(
218+
'bin',
219+
'internal',
220+
'last_engine_commit.${platform.isWindows || forcePowershell ? 'ps1' : 'sh'}',
221+
),
222+
),
223+
);
224+
}
225+
226+
factory _FlutterRootUnderTest.findWithin({
227+
String? path,
228+
FileSystem fileSystem = const LocalFileSystem(),
229+
bool forcePowershell = false,
230+
}) {
231+
path ??= fileSystem.currentDirectory.path;
232+
Directory current = fileSystem.directory(path);
233+
while (!current.childFile('DEPS').existsSync()) {
234+
if (current.path == current.parent.path) {
235+
throw ArgumentError.value(path, 'path', 'Could not resolve flutter root');
236+
}
237+
current = current.parent;
238+
}
239+
return _FlutterRootUnderTest.fromPath(current.path, forcePowershell: forcePowershell);
240+
}
241+
242+
const _FlutterRootUnderTest._(
243+
this.root, {
244+
required this.binInternalLastEngineCommit,
245+
required this.depsFile,
246+
required this.engineDirectory,
247+
});
248+
249+
final Directory root;
250+
251+
/// `DEPS`.
252+
final File depsFile;
253+
254+
/// The `engine/` directory.
255+
final Directory engineDirectory;
256+
257+
/// `bin/internal/last_engine_commit.{sh|ps1}`.
258+
final File binInternalLastEngineCommit;
259+
}

docs/tool/Engine-artifacts.md

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,17 @@ On Cocoon (Flutter's internal CI/CD) we _often_ set
6161
| `main` | `commit.sha` | _Uses normal flow_ | _Uses normal flow_ |
6262
| `flutter-x.x-candidate.x` | `commit.sha` | N/A[^1] | `commit.sha` |
6363

64-
> NOTE: `engine.version` is intentionally ignored in release candidate
65-
> post-submit builds. See
66-
> [#167010](https://github.com/flutter/flutter/issues/167010).
64+
> IMPORTANT: `engine.version` is intentionally ignored in release candidate
65+
> post-submit builds.
66+
>
67+
> To generate a new `engine.version`:
68+
>
69+
> ```sh
70+
> ./bin/internal/last_engine_commit.sh > ./bin/internal/engine.version
71+
> ```
72+
>
73+
> At the moment this needs to be manually done, and manually verified, before
74+
> making a release. See [#168273](https://github.com/flutter/flutter/issues/168273).
6775
6876
[^1]: Release candidates do not use a merge queue.
6977
@@ -74,6 +82,9 @@ The script(s) that compute (and test the computation of) the engine version:
7482
- [`bin/internal/update_engine_version.sh`](../../bin/internal/update_engine_version.sh)
7583
- [`bin/internal/update_engine_version.ps1`](../../bin/internal/update_engine_version.ps1)
7684
- [`dev/tools/test/update_engine_version_test.dart`](../../dev/tools/test/update_engine_version_test.dart)
85+
- [`bin/internal/last_engine_commit.sh`](../../bin/internal/last_engine_commit.sh)
86+
- [`bin/internal/last_engine_commit.ps1`](../../bin/internal/last_engine_commit.ps1)
87+
- [`dev/tools/test/last_engine_commit_test.dart`](../../dev/tools/test/last_engine_commit_test.dart)
7788
- [`bin/internal/content_aware_hash.sh`](../../bin/internal/content_aware_hash.sh)
7889
- [`bin/internal/content_aware_hash.ps1`](../../bin/internal/content_aware_hash.ps1)
7990
- [`dev/tools/test/content_aware_hash_test.dart`](../../dev/tools/test/content_aware_hash_test.dart)

0 commit comments

Comments
 (0)