[Windows] Set Platform.executable on engine start (#35560)
When setting FlutterProjectArgs.command_line_argv prior to launching the
engine, we were previously setting a placeholder value rather than the
executable name. This resulted in Platform.executable (from dart:io)
returning "placeholder" in application code.
This updates the Windows implementation for consistency with macOS and
guarantees that Platform.executable will return a reasonable value in
Dart code.
Note that this does not affect Platform.resolvedExecutable, which returns
a full, resolved path, and is implemented in the Dart runtime itself.
Previously the code:
print(Platform.executable);
print(Platform.resolvedExecutable);
resulted in the following output on Windows:
flutter: placeholder
flutter: C:\path\to\project\build\windows\runner\Debug\project.exe
after this patch, it results in:
flutter: project.exe
flutter: C:\path\to\project\build\windows\runner\Debug\project.exe
Issue: https://github.com/flutter/flutter/issues/83921
diff --git a/shell/platform/windows/fixtures/main.dart b/shell/platform/windows/fixtures/main.dart
index e2d76aa..57ba615 100644
--- a/shell/platform/windows/fixtures/main.dart
+++ b/shell/platform/windows/fixtures/main.dart
@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+import 'dart:io' as io;
import 'dart:ui' as ui;
// Signals a waiting latch in the native test.
@@ -10,6 +11,9 @@
// Signals a waiting latch in the native test, passing a boolean value.
void signalBoolValue(bool value) native 'SignalBoolValue';
+// Signals a waiting latch in the native test, passing a string value.
+void signalStringValue(String value) native 'SignalStringValue';
+
// Signals a waiting latch in the native test, which returns a value to the fixture.
bool signalBoolReturn() native 'SignalBoolReturn';
@@ -40,6 +44,11 @@
}
@pragma('vm:entry-point')
+void readPlatformExecutable() {
+ signalStringValue(io.Platform.executable);
+}
+
+@pragma('vm:entry-point')
void drawHelloWorld() {
ui.PlatformDispatcher.instance.onBeginFrame = (Duration duration) {
final ui.ParagraphBuilder paragraphBuilder = ui.ParagraphBuilder(ui.ParagraphStyle())
diff --git a/shell/platform/windows/flutter_windows_engine.cc b/shell/platform/windows/flutter_windows_engine.cc
index 6c3d728..9396a9e 100644
--- a/shell/platform/windows/flutter_windows_engine.cc
+++ b/shell/platform/windows/flutter_windows_engine.cc
@@ -11,6 +11,7 @@
#include <sstream>
#include "flutter/fml/logging.h"
+#include "flutter/fml/paths.h"
#include "flutter/fml/platform/win/wstring_conversion.h"
#include "flutter/shell/platform/common/client_wrapper/binary_messenger_impl.h"
#include "flutter/shell/platform/common/path_utils.h"
@@ -224,8 +225,9 @@
// FlutterProjectArgs is expecting a full argv, so when processing it for
// flags the first item is treated as the executable and ignored. Add a dummy
// value so that all provided arguments are used.
+ std::string executable_name = GetExecutableName();
+ std::vector<const char*> argv = {executable_name.c_str()};
std::vector<std::string> switches = project_->GetSwitches();
- std::vector<const char*> argv = {"placeholder"};
std::transform(
switches.begin(), switches.end(), std::back_inserter(argv),
[](const std::string& arg) -> const char* { return arg.c_str(); });
@@ -532,10 +534,9 @@
// Convert the locale list to the locale pointer list that must be provided.
std::vector<const FlutterLocale*> flutter_locale_list;
flutter_locale_list.reserve(flutter_locales.size());
- std::transform(
- flutter_locales.begin(), flutter_locales.end(),
- std::back_inserter(flutter_locale_list),
- [](const auto& arg) -> const auto* { return &arg; });
+ std::transform(flutter_locales.begin(), flutter_locales.end(),
+ std::back_inserter(flutter_locale_list),
+ [](const auto& arg) -> const auto* { return &arg; });
embedder_api_.UpdateLocales(engine_, flutter_locale_list.data(),
flutter_locale_list.size());
}
@@ -592,4 +593,18 @@
return node_delegate->GetNativeViewAccessible();
}
+std::string FlutterWindowsEngine::GetExecutableName() const {
+ std::pair<bool, std::string> result = fml::paths::GetExecutablePath();
+ if (result.first) {
+ const std::string& executable_path = result.second;
+ size_t last_separator = executable_path.find_last_of("/\\");
+ if (last_separator == std::string::npos ||
+ last_separator == executable_path.size() - 1) {
+ return executable_path;
+ }
+ return executable_path.substr(last_separator + 1);
+ }
+ return "Flutter";
+}
+
} // namespace flutter
diff --git a/shell/platform/windows/flutter_windows_engine.h b/shell/platform/windows/flutter_windows_engine.h
index 3ead422..700f788 100644
--- a/shell/platform/windows/flutter_windows_engine.h
+++ b/shell/platform/windows/flutter_windows_engine.h
@@ -220,6 +220,9 @@
root_isolate_create_callback_ = callback;
}
+ // Returns the executable name for this process or "Flutter" if unknown.
+ std::string GetExecutableName() const;
+
private:
// Allows swapping out embedder_api_ calls in tests.
friend class EngineModifier;
diff --git a/shell/platform/windows/flutter_windows_engine_unittests.cc b/shell/platform/windows/flutter_windows_engine_unittests.cc
index 87315ef..1417fe4 100644
--- a/shell/platform/windows/flutter_windows_engine_unittests.cc
+++ b/shell/platform/windows/flutter_windows_engine_unittests.cc
@@ -404,5 +404,10 @@
EXPECT_TRUE(called);
}
+TEST(FlutterWindowsEngine, GetExecutableName) {
+ std::unique_ptr<FlutterWindowsEngine> engine = GetTestEngine();
+ EXPECT_EQ(engine->GetExecutableName(), "flutter_windows_unittests.exe");
+}
+
} // namespace testing
} // namespace flutter
diff --git a/shell/platform/windows/flutter_windows_unittests.cc b/shell/platform/windows/flutter_windows_unittests.cc
index 720c7b3..40cd9e3 100644
--- a/shell/platform/windows/flutter_windows_unittests.cc
+++ b/shell/platform/windows/flutter_windows_unittests.cc
@@ -118,6 +118,30 @@
EXPECT_TRUE(bool_value);
}
+// Verify that Platform.executable returns the executable name.
+TEST_F(WindowsTest, PlatformExecutable) {
+ auto& context = GetContext();
+ WindowsConfigBuilder builder(context);
+ builder.SetDartEntrypoint("readPlatformExecutable");
+
+ std::string executable_name;
+ fml::AutoResetWaitableEvent latch;
+ auto native_entry = CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
+ auto handle = Dart_GetNativeArgument(args, 0);
+ ASSERT_FALSE(Dart_IsError(handle));
+ executable_name = tonic::DartConverter<std::string>::FromDart(handle);
+ latch.Signal();
+ });
+ context.AddNativeFunction("SignalStringValue", native_entry);
+
+ ViewControllerPtr controller{builder.Run()};
+ ASSERT_NE(controller, nullptr);
+
+ // Wait until signalStringValue has been called.
+ latch.Wait();
+ EXPECT_EQ(executable_name, "flutter_windows_unittests.exe");
+}
+
// Verify that native functions that return values can be registered and
// resolved.
TEST_F(WindowsTest, VerifyNativeFunctionWithReturn) {