fix: select correct executable in Windows preview (#3492)

This commit is contained in:
Eric Seidel
2026-03-21 08:51:25 -07:00
committed by GitHub
parent f049f4f126
commit 15daeaf30a
3 changed files with 32 additions and 5 deletions
@@ -462,8 +462,8 @@ This is only applicable when previewing Android releases.''',
channel: track.channel,
);
final exeFile = appDirectory.listSync().whereType<File>().firstWhere(
(file) => file.path.endsWith('.exe'),
final exeFile = windows.findExecutable(
releaseDirectory: appDirectory,
);
return startAndForwardOutput(exeFile.path);
@@ -1,3 +1,4 @@
// cspell:words crashpad
import 'dart:io';
import 'package:collection/collection.dart';
@@ -19,19 +20,31 @@ final windowsRef = create(Windows.new);
/// The [Windows] instance available in the current zone.
Windows get windows => read(windowsRef);
/// Executables bundled with Flutter Windows builds that are not the app.
const defaultIgnoredExecutables = {'crashpad_handler.exe'};
/// A class that provides Windows-specific functionality.
class Windows {
/// Returns the selected application `.exe` from [releaseDirectory].
/// Searches for an exact match for [projectName] and if none is found,
/// falls back to returning the most recently modified executable.
/// If [projectName] is provided, searches for an exact match first.
/// Falls back to returning the most recently modified executable,
/// excluding [ignoredExecutables] (defaults to [defaultIgnoredExecutables]).
///
/// Note: when files are extracted from a zip, they may all have similar
/// timestamps, making the mtime fallback unreliable. Prefer passing
/// [projectName] when available.
File findExecutable({
required Directory releaseDirectory,
required String projectName,
String? projectName,
Set<String> ignoredExecutables = defaultIgnoredExecutables,
}) {
final executables = releaseDirectory
.listSync()
.whereType<File>()
.where((f) => p.extension(f.path).toLowerCase() == '.exe')
.where(
(f) => !ignoredExecutables.contains(p.basename(f.path)),
)
.sorted((a, b) => b.lastModifiedSync().compareTo(a.lastModifiedSync()));
if (executables.isEmpty) {
@@ -2388,6 +2388,7 @@ channel: ${DeploymentTrack.staging.channel}
late ShorebirdProcess shorebirdProcess;
late Process process;
late Directory windowsReleaseDirectory;
late Windows windowsMock;
R runWithOverrides<R>(R Function() body) {
return HttpOverrides.runZoned(
@@ -2406,6 +2407,7 @@ channel: ${DeploymentTrack.staging.channel}
processRef.overrideWith(() => shorebirdProcess),
shorebirdEnvRef.overrideWith(() => shorebirdEnv),
shorebirdValidatorRef.overrideWith(() => shorebirdValidator),
windowsRef.overrideWith(() => windowsMock),
},
),
);
@@ -2435,6 +2437,18 @@ channel: ${DeploymentTrack.staging.channel}
setUp(() {
shorebirdProcess = MockShorebirdProcess();
process = MockProcess();
windowsMock = MockWindows();
when(
() => windowsMock.findExecutable(
releaseDirectory: any(named: 'releaseDirectory'),
projectName: any(named: 'projectName'),
),
).thenAnswer((invocation) {
final dir = invocation.namedArguments[#releaseDirectory] as Directory;
return dir.listSync().whereType<File>().firstWhere(
(f) => f.path.endsWith('.exe'),
);
});
windowsReleaseArtifact = MockReleaseArtifact();
final tempDir = Directory.systemTemp.createTempSync();