Skip to content

Commit 41ce791

Browse files
[macos] Allow engine flags via environment vars (flutter#21468)
Replaces the (temporary) compile-time option to pass engine switches with the ability to pass them temporarily at runtime via environment variables. This moves the recently-added code for doing this on Windows to a shared location for use by all desktop embeddings. This is enabled only for debug/profile to avoid potential issues with tampering with released applications, but if there is a need for that in the future it could be added (potentially with a whitelist, as is currently used for Dart VM flags). Temporarily adds a way to enable mirrors as a compile time option, as is already provided in the Linux embedding, to provide a migration path for the one remaining known need for compile-time options that has been raised in flutter#38569.
1 parent 1d4d69d commit 41ce791

File tree

13 files changed

+209
-84
lines changed

13 files changed

+209
-84
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -878,6 +878,9 @@ FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/plugin_registrar
878878
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/standard_codec.cc
879879
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/standard_message_codec_unittests.cc
880880
FILE: ../../../flutter/shell/platform/common/cpp/client_wrapper/standard_method_codec_unittests.cc
881+
FILE: ../../../flutter/shell/platform/common/cpp/engine_switches.cc
882+
FILE: ../../../flutter/shell/platform/common/cpp/engine_switches.h
883+
FILE: ../../../flutter/shell/platform/common/cpp/engine_switches_unittests.cc
881884
FILE: ../../../flutter/shell/platform/common/cpp/incoming_message_dispatcher.cc
882885
FILE: ../../../flutter/shell/platform/common/cpp/incoming_message_dispatcher.h
883886
FILE: ../../../flutter/shell/platform/common/cpp/json_message_codec.cc

shell/platform/common/cpp/BUILD.gn

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
# Use of this source code is governed by a BSD-style license that can be
33
# found in the LICENSE file.
44

5+
import("//flutter/common/config.gni")
56
import("//flutter/testing/testing.gni")
67

78
config("desktop_library_implementation") {
@@ -42,12 +43,25 @@ source_set("common_cpp_input") {
4243

4344
configs += [ ":desktop_library_implementation" ]
4445

46+
public_configs = [ "//flutter:config" ]
47+
4548
if (is_win) {
4649
# For wstring_conversion. See issue #50053.
4750
defines = [ "_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING" ]
4851
}
4952
}
5053

54+
source_set("common_cpp_switches") {
55+
public = [ "engine_switches.h" ]
56+
57+
sources = [ "engine_switches.cc" ]
58+
59+
public_configs = [
60+
"//flutter:config",
61+
"//flutter/common:flutter_config",
62+
]
63+
}
64+
5165
source_set("common_cpp") {
5266
public = [
5367
"incoming_message_dispatcher.h",
@@ -65,6 +79,8 @@ source_set("common_cpp") {
6579

6680
configs += [ ":desktop_library_implementation" ]
6781

82+
public_configs = [ "//flutter:config" ]
83+
6884
deps = [
6985
":common_cpp_library_headers",
7086
"//flutter/shell/platform/common/cpp/client_wrapper:client_wrapper",
@@ -85,6 +101,8 @@ source_set("common_cpp_core") {
85101
public = [ "path_utils.h" ]
86102

87103
sources = [ "path_utils.cc" ]
104+
105+
public_configs = [ "//flutter:config" ]
88106
}
89107

90108
if (enable_unittests) {
@@ -115,6 +133,7 @@ if (enable_unittests) {
115133
testonly = true
116134

117135
sources = [
136+
"engine_switches_unittests.cc",
118137
"json_message_codec_unittests.cc",
119138
"json_method_codec_unittests.cc",
120139
"text_input_model_unittests.cc",
@@ -124,6 +143,7 @@ if (enable_unittests) {
124143
":common_cpp",
125144
":common_cpp_fixtures",
126145
":common_cpp_input",
146+
":common_cpp_switches",
127147
"//flutter/shell/platform/common/cpp/client_wrapper:client_wrapper",
128148
"//flutter/shell/platform/common/cpp/client_wrapper:client_wrapper_library_stubs",
129149
"//flutter/testing",
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright 2013 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+
#include "flutter/shell/platform/common/cpp/engine_switches.h"
6+
7+
#include <algorithm>
8+
#include <cstdlib>
9+
#include <iostream>
10+
#include <sstream>
11+
12+
namespace flutter {
13+
14+
std::vector<std::string> GetSwitchesFromEnvironment() {
15+
std::vector<std::string> switches;
16+
// Read engine switches from the environment in debug/profile. If release mode
17+
// support is needed in the future, it should likely use a whitelist.
18+
#ifndef FLUTTER_RELEASE
19+
const char* switch_count_key = "FLUTTER_ENGINE_SWITCHES";
20+
const int kMaxSwitchCount = 50;
21+
const char* switch_count_string = std::getenv(switch_count_key);
22+
int switch_count = std::min(
23+
kMaxSwitchCount, switch_count_string ? atoi(switch_count_string) : 0);
24+
for (int i = 1; i <= switch_count; ++i) {
25+
std::ostringstream switch_key;
26+
switch_key << "FLUTTER_ENGINE_SWITCH_" << i;
27+
const char* switch_value = std::getenv(switch_key.str().c_str());
28+
if (switch_value) {
29+
std::ostringstream switch_value_as_flag;
30+
switch_value_as_flag << "--" << switch_value;
31+
switches.push_back(switch_value_as_flag.str());
32+
} else {
33+
std::cerr << switch_count << " keys expected from " << switch_count_key
34+
<< ", but " << switch_key.str() << " is missing." << std::endl;
35+
}
36+
}
37+
#endif // !FLUTTER_RELEASE
38+
return switches;
39+
}
40+
41+
} // namespace flutter
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
// Copyright 2013 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+
#ifndef FLUTTER_SHELL_PLATFORM_COMMON_CPP_ENGINE_SWITCHES_H_
6+
#define FLUTTER_SHELL_PLATFORM_COMMON_CPP_ENGINE_SWITCHES_H_
7+
8+
#include <string>
9+
#include <vector>
10+
11+
namespace flutter {
12+
13+
// Returns an array of engine switches suitable to pass to the embedder API
14+
// in FlutterProjectArgs, based on parsing variables from the environment in
15+
// the form:
16+
// FLUTTER_ENGINE_SWITCHES=<count>
17+
// FLUTTER_ENGINE_SWITCH_1=...
18+
// FLUTTER_ENGINE_SWITCH_2=...
19+
// ...
20+
// Values should match those in shell/common/switches.h
21+
//
22+
// The returned array does not include the initial dummy argument expected by
23+
// the embedder API, so command_line_argv should not be set directly from it.
24+
//
25+
// In release mode, not all switches from the environment will necessarily be
26+
// returned. See the implementation for details.
27+
std::vector<std::string> GetSwitchesFromEnvironment();
28+
29+
} // namespace flutter
30+
31+
#endif // FLUTTER_SHELL_PLATFORM_COMMON_CPP_ENGINE_SWITCHES_H_
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
// Copyright 2013 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+
#include "flutter/shell/platform/common/cpp/engine_switches.h"
6+
7+
#include "gtest/gtest.h"
8+
9+
namespace flutter {
10+
11+
namespace {
12+
// Sets |key=value| in the environment of this process.
13+
void SetEnvironmentVariable(const char* key, const char* value) {
14+
#ifdef _WIN32
15+
_putenv_s(key, value);
16+
#else
17+
setenv(key, value, 1);
18+
#endif
19+
}
20+
21+
// Removes |key| from the environment of this process, if present.
22+
void ClearEnvironmentVariable(const char* key) {
23+
#ifdef _WIN32
24+
_putenv_s(key, "");
25+
#else
26+
unsetenv(key);
27+
#endif
28+
}
29+
} // namespace
30+
31+
TEST(FlutterProjectBundle, SwitchesEmpty) {
32+
// Clear the main environment variable, since test order is not guaranteed.
33+
ClearEnvironmentVariable("FLUTTER_ENGINE_SWITCHES");
34+
35+
EXPECT_EQ(GetSwitchesFromEnvironment().size(), 0U);
36+
}
37+
38+
#ifdef FLUTTER_RELEASE
39+
TEST(FlutterProjectBundle, SwitchesIgnoredInRelease) {
40+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCHES", "2");
41+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCH_1", "abc");
42+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCH_2", "foo=\"bar, baz\"");
43+
44+
std::vector<std::string> switches = GetSwitchesFromEnvironment();
45+
EXPECT_EQ(switches.size(), 0U);
46+
}
47+
#endif // FLUTTER_RELEASE
48+
49+
#ifndef FLUTTER_RELEASE
50+
TEST(FlutterProjectBundle, Switches) {
51+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCHES", "2");
52+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCH_1", "abc");
53+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCH_2", "foo=\"bar, baz\"");
54+
55+
std::vector<std::string> switches = GetSwitchesFromEnvironment();
56+
EXPECT_EQ(switches.size(), 2U);
57+
EXPECT_EQ(switches[0], "--abc");
58+
EXPECT_EQ(switches[1], "--foo=\"bar, baz\"");
59+
}
60+
61+
TEST(FlutterProjectBundle, SwitchesExtraValues) {
62+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCHES", "1");
63+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCH_1", "abc");
64+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCH_2", "foo=\"bar, baz\"");
65+
66+
std::vector<std::string> switches = GetSwitchesFromEnvironment();
67+
EXPECT_EQ(switches.size(), 1U);
68+
EXPECT_EQ(switches[0], "--abc");
69+
}
70+
71+
TEST(FlutterProjectBundle, SwitchesMissingValues) {
72+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCHES", "4");
73+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCH_1", "abc");
74+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCH_2", "foo=\"bar, baz\"");
75+
ClearEnvironmentVariable("FLUTTER_ENGINE_SWITCH_3");
76+
SetEnvironmentVariable("FLUTTER_ENGINE_SWITCH_4", "oops");
77+
78+
std::vector<std::string> switches = GetSwitchesFromEnvironment();
79+
EXPECT_EQ(switches.size(), 3U);
80+
EXPECT_EQ(switches[0], "--abc");
81+
EXPECT_EQ(switches[1], "--foo=\"bar, baz\"");
82+
// The missing switch should be skipped, leaving SWITCH_4 as the third
83+
// switch in the array.
84+
EXPECT_EQ(switches[2], "--oops");
85+
}
86+
#endif // !FLUTTER_RELEASE
87+
88+
} // namespace flutter

shell/platform/darwin/macos/BUILD.gn

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,6 +67,7 @@ source_set("flutter_framework_source") {
6767
sources += _flutter_framework_headers
6868

6969
deps = [
70+
"//flutter/shell/platform/common/cpp:common_cpp_switches",
7071
"//flutter/shell/platform/darwin/common:framework_shared",
7172
"//flutter/shell/platform/embedder:embedder_as_internal_library",
7273
]

shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -30,15 +30,11 @@ FLUTTER_EXPORT
3030
NS_DESIGNATED_INITIALIZER;
3131

3232
/**
33-
* Switches to pass to the Flutter engine. See
34-
* https://github.com/flutter/engine/blob/master/shell/common/switches.h
35-
* for details. Not all switches will apply to embedding mode. Switches have not stability
36-
* guarantee, and are subject to change without notice.
33+
* If set, allows the Flutter project to use the dart:mirrors library.
3734
*
38-
* Note: This property WILL BE REMOVED in the future. If you use this property, please see
39-
* https://github.com/flutter/flutter/issues/38569.
35+
* Deprecated: This function is temporary and will be removed in a future release.
4036
*/
41-
@property(nullable) NSArray<NSString*>* engineSwitches;
37+
@property(nonatomic) bool enableMirrors;
4238

4339
@end
4440

shell/platform/darwin/macos/framework/Source/FlutterDartProject.mm

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
#include <vector>
99

10+
#include "flutter/shell/platform/common/cpp/engine_switches.h"
11+
1012
static NSString* const kICUBundlePath = @"icudtl.dat";
1113
static NSString* const kAppBundleIdentifier = @"io.flutter.flutter.app";
1214

@@ -67,13 +69,10 @@ - (NSString*)ICUDataPath {
6769
return path;
6870
}
6971

70-
- (std::vector<const char*>)argv {
71-
// FlutterProjectArgs expects a full argv, so when processing it for flags the first item is
72-
// treated as the executable and ignored. Add a dummy value so that all provided arguments
73-
// are used.
74-
std::vector<const char*> arguments = {"placeholder"};
75-
for (NSUInteger i = 0; i < _engineSwitches.count; ++i) {
76-
arguments.push_back([_engineSwitches[i] UTF8String]);
72+
- (std::vector<std::string>)switches {
73+
std::vector<std::string> arguments = flutter::GetSwitchesFromEnvironment();
74+
if (self.enableMirrors) {
75+
arguments.push_back("--dart-flags=--enable_mirrors=true");
7776
}
7877
return arguments;
7978
}

shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77

88
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterDartProject.h"
99

10+
#include <string>
1011
#include <vector>
1112

1213
/**
@@ -26,12 +27,8 @@
2627

2728
/**
2829
* The command line arguments array for the engine.
29-
*
30-
* WARNING: The pointers in this array are valid only until the next call to set `engineSwitches`.
31-
* The returned vector should be used immediately, then discarded. It is returned this way for
32-
* ease of use with FlutterProjectArgs.
3330
*/
34-
@property(nonatomic, readonly) std::vector<const char*> argv;
31+
@property(nonatomic, readonly) std::vector<std::string> switches;
3532

3633
/**
3734
* Instead of looking up the assets and ICU data path in the application bundle, this initializer

shell/platform/darwin/macos/framework/Source/FlutterEngine.mm

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
#import "flutter/shell/platform/darwin/macos/framework/Headers/FlutterEngine.h"
77
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterEngine_Internal.h"
88

9+
#include <algorithm>
910
#include <vector>
1011

1112
#import "flutter/shell/platform/darwin/macos/framework/Source/FlutterDartProject_Internal.h"
@@ -236,13 +237,20 @@ - (BOOL)runWithEntrypoint:(NSString*)entrypoint {
236237

237238
// TODO(stuartmorgan): Move internal channel registration from FlutterViewController to here.
238239

240+
// FlutterProjectArgs is expecting a full argv, so when processing it for
241+
// flags the first item is treated as the executable and ignored. Add a dummy
242+
// value so that all provided arguments are used.
243+
std::vector<std::string> switches = _project.switches;
244+
std::vector<const char*> argv = {"placeholder"};
245+
std::transform(switches.begin(), switches.end(), std::back_inserter(argv),
246+
[](const std::string& arg) -> const char* { return arg.c_str(); });
247+
239248
FlutterProjectArgs flutterArguments = {};
240249
flutterArguments.struct_size = sizeof(FlutterProjectArgs);
241250
flutterArguments.assets_path = _project.assetsPath.UTF8String;
242251
flutterArguments.icu_data_path = _project.ICUDataPath.UTF8String;
243-
std::vector<const char*> arguments = _project.argv;
244-
flutterArguments.command_line_argc = static_cast<int>(arguments.size());
245-
flutterArguments.command_line_argv = &arguments[0];
252+
flutterArguments.command_line_argc = static_cast<int>(argv.size());
253+
flutterArguments.command_line_argv = argv.size() > 0 ? argv.data() : nullptr;
246254
flutterArguments.platform_message_callback = (FlutterPlatformMessageCallback)OnPlatformMessage;
247255
flutterArguments.custom_dart_entrypoint = entrypoint.UTF8String;
248256
static size_t sTaskRunnerIdentifiers = 0;

0 commit comments

Comments
 (0)