| // Copyright 2013 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. |
| |
| #include <algorithm> |
| #include <iomanip> |
| #include <iostream> |
| #include <iterator> |
| #include <sstream> |
| #include <string> |
| |
| #include "flutter/fml/native_library.h" |
| #include "flutter/fml/paths.h" |
| #include "flutter/fml/size.h" |
| #include "flutter/shell/version/version.h" |
| |
| // Include once for the default enum definition. |
| #include "flutter/shell/common/switches.h" |
| |
| #undef SHELL_COMMON_SWITCHES_H_ |
| |
| struct SwitchDesc { |
| flutter::Switch sw; |
| const std::string_view flag; |
| const char* help; |
| }; |
| |
| #undef DEF_SWITCHES_START |
| #undef DEF_SWITCH |
| #undef DEF_SWITCHES_END |
| |
| // clang-format off |
| #define DEF_SWITCHES_START static const struct SwitchDesc gSwitchDescs[] = { |
| #define DEF_SWITCH(p_swtch, p_flag, p_help) \ |
| { flutter::Switch:: p_swtch, p_flag, p_help }, |
| #define DEF_SWITCHES_END }; |
| // clang-format on |
| |
| // List of common and safe VM flags to allow to be passed directly to the VM. |
| #if FLUTTER_RELEASE |
| |
| // clang-format off |
| static const std::string kAllowedDartFlags[] = { |
| "--enable-isolate-groups", |
| "--no-enable-isolate-groups", |
| }; |
| // clang-format on |
| |
| #else |
| |
| // clang-format off |
| static const std::string kAllowedDartFlags[] = { |
| "--enable-isolate-groups", |
| "--no-enable-isolate-groups", |
| "--enable_mirrors", |
| "--enable-service-port-fallback", |
| "--max_profile_depth", |
| "--profile_period", |
| "--random_seed", |
| "--sample-buffer-duration", |
| "--trace-kernel", |
| "--trace-reload", |
| "--trace-reload-verbose", |
| "--write-service-info", |
| "--null_assertions", |
| "--strict_null_safety_checks", |
| "--max_subtype_cache_entries", |
| }; |
| // clang-format on |
| |
| #endif // FLUTTER_RELEASE |
| |
| // Include again for struct definition. |
| #include "flutter/shell/common/switches.h" |
| |
| // Define symbols for the ICU data that is linked into the Flutter library on |
| // Android. This is a workaround for crashes seen when doing dynamic lookups |
| // of the engine's own symbols on some older versions of Android. |
| #if FML_OS_ANDROID |
| extern uint8_t _binary_icudtl_dat_start[]; |
| extern uint8_t _binary_icudtl_dat_end[]; |
| |
| static std::unique_ptr<fml::Mapping> GetICUStaticMapping() { |
| return std::make_unique<fml::NonOwnedMapping>( |
| _binary_icudtl_dat_start, |
| _binary_icudtl_dat_end - _binary_icudtl_dat_start); |
| } |
| #endif |
| |
| namespace flutter { |
| |
| void PrintUsage(const std::string& executable_name) { |
| std::cerr << std::endl << " " << executable_name << std::endl << std::endl; |
| |
| std::cerr << "Versions: " << std::endl << std::endl; |
| |
| std::cerr << "Flutter Engine Version: " << GetFlutterEngineVersion() |
| << std::endl; |
| std::cerr << "Skia Version: " << GetSkiaVersion() << std::endl; |
| |
| std::cerr << "Dart Version: " << GetDartVersion() << std::endl << std::endl; |
| |
| std::cerr << "Available Flags:" << std::endl; |
| |
| const uint32_t column_width = 80; |
| |
| const uint32_t flags_count = static_cast<uint32_t>(Switch::Sentinel); |
| |
| uint32_t max_width = 2; |
| for (uint32_t i = 0; i < flags_count; i++) { |
| auto desc = gSwitchDescs[i]; |
| max_width = std::max<uint32_t>(desc.flag.size() + 2, max_width); |
| } |
| |
| const uint32_t help_width = column_width - max_width - 3; |
| |
| std::cerr << std::string(column_width, '-') << std::endl; |
| for (uint32_t i = 0; i < flags_count; i++) { |
| auto desc = gSwitchDescs[i]; |
| |
| std::cerr << std::setw(max_width) |
| << std::string("--") + |
| std::string{desc.flag.data(), desc.flag.size()} |
| << " : "; |
| |
| std::istringstream stream(desc.help); |
| int32_t remaining = help_width; |
| |
| std::string word; |
| while (stream >> word && remaining > 0) { |
| remaining -= (word.size() + 1); |
| if (remaining <= 0) { |
| std::cerr << std::endl |
| << std::string(max_width, ' ') << " " << word << " "; |
| remaining = help_width; |
| } else { |
| std::cerr << word << " "; |
| } |
| } |
| |
| std::cerr << std::endl; |
| } |
| std::cerr << std::string(column_width, '-') << std::endl; |
| } |
| |
| const std::string_view FlagForSwitch(Switch swtch) { |
| for (uint32_t i = 0; i < static_cast<uint32_t>(Switch::Sentinel); i++) { |
| if (gSwitchDescs[i].sw == swtch) { |
| return gSwitchDescs[i].flag; |
| } |
| } |
| return std::string_view(); |
| } |
| |
| static std::vector<std::string> ParseCommaDelimited(const std::string& input) { |
| std::istringstream ss(input); |
| std::vector<std::string> result; |
| std::string token; |
| while (std::getline(ss, token, ',')) { |
| result.push_back(token); |
| } |
| return result; |
| } |
| |
| static bool IsAllowedDartVMFlag(const std::string& flag) { |
| for (uint32_t i = 0; i < fml::size(kAllowedDartFlags); ++i) { |
| const std::string& allowed = kAllowedDartFlags[i]; |
| // Check that the prefix of the flag matches one of the allowed flags. This |
| // is to handle cases where flags take arguments, such as in |
| // "--max_profile_depth 1". |
| // |
| // We don't need to worry about cases like "--safe --sneaky_dangerous" as |
| // the VM will discard these as a single unrecognized flag. |
| if (flag.length() >= allowed.length() && |
| std::equal(allowed.begin(), allowed.end(), flag.begin())) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| template <typename T> |
| static bool GetSwitchValue(const fml::CommandLine& command_line, |
| Switch sw, |
| T* result) { |
| std::string switch_string; |
| |
| if (!command_line.GetOptionValue(FlagForSwitch(sw), &switch_string)) { |
| return false; |
| } |
| |
| std::stringstream stream(switch_string); |
| T value = 0; |
| if (stream >> value) { |
| *result = value; |
| return true; |
| } |
| |
| return false; |
| } |
| |
| std::unique_ptr<fml::Mapping> GetSymbolMapping( |
| const std::string& symbol_prefix, |
| const std::string& native_lib_path) { |
| const uint8_t* mapping = nullptr; |
| intptr_t size; |
| |
| auto lookup_symbol = [&mapping, &size, symbol_prefix]( |
| const fml::RefPtr<fml::NativeLibrary>& library) { |
| mapping = library->ResolveSymbol((symbol_prefix + "_start").c_str()); |
| size = reinterpret_cast<intptr_t>( |
| library->ResolveSymbol((symbol_prefix + "_size").c_str())); |
| }; |
| |
| fml::RefPtr<fml::NativeLibrary> library = |
| fml::NativeLibrary::CreateForCurrentProcess(); |
| lookup_symbol(library); |
| |
| if (!(mapping && size)) { |
| // Symbol lookup for the current process fails on some devices. As a |
| // fallback, try doing the lookup based on the path to the Flutter library. |
| library = fml::NativeLibrary::Create(native_lib_path.c_str()); |
| lookup_symbol(library); |
| } |
| |
| FML_CHECK(mapping && size) << "Unable to resolve symbols: " << symbol_prefix; |
| return std::make_unique<fml::NonOwnedMapping>(mapping, size); |
| } |
| |
| Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { |
| Settings settings = {}; |
| |
| // Set executable name. |
| if (command_line.has_argv0()) { |
| settings.executable_name = command_line.argv0(); |
| } |
| |
| // Enable Observatory |
| settings.enable_observatory = |
| !command_line.HasOption(FlagForSwitch(Switch::DisableObservatory)); |
| |
| // Enable mDNS Observatory Publication |
| settings.enable_observatory_publication = !command_line.HasOption( |
| FlagForSwitch(Switch::DisableObservatoryPublication)); |
| |
| // Set Observatory Host |
| if (command_line.HasOption(FlagForSwitch(Switch::DeviceObservatoryHost))) { |
| command_line.GetOptionValue(FlagForSwitch(Switch::DeviceObservatoryHost), |
| &settings.observatory_host); |
| } |
| // Default the observatory port based on --ipv6 if not set. |
| if (settings.observatory_host.empty()) { |
| settings.observatory_host = |
| command_line.HasOption(FlagForSwitch(Switch::IPv6)) ? "::1" |
| : "127.0.0.1"; |
| } |
| |
| // Set Observatory Port |
| if (command_line.HasOption(FlagForSwitch(Switch::DeviceObservatoryPort))) { |
| if (!GetSwitchValue(command_line, Switch::DeviceObservatoryPort, |
| &settings.observatory_port)) { |
| FML_LOG(INFO) |
| << "Observatory port specified was malformed. Will default to " |
| << settings.observatory_port; |
| } |
| } |
| |
| settings.may_insecurely_connect_to_all_domains = !command_line.HasOption( |
| FlagForSwitch(Switch::DisallowInsecureConnections)); |
| |
| command_line.GetOptionValue(FlagForSwitch(Switch::DomainNetworkPolicy), |
| &settings.domain_network_policy); |
| |
| // Disable need for authentication codes for VM service communication, if |
| // specified. |
| settings.disable_service_auth_codes = |
| command_line.HasOption(FlagForSwitch(Switch::DisableServiceAuthCodes)); |
| |
| // Allow fallback to automatic port selection if binding to a specified port |
| // fails. |
| settings.enable_service_port_fallback = |
| command_line.HasOption(FlagForSwitch(Switch::EnableServicePortFallback)); |
| |
| // Checked mode overrides. |
| settings.disable_dart_asserts = |
| command_line.HasOption(FlagForSwitch(Switch::DisableDartAsserts)); |
| |
| settings.start_paused = |
| command_line.HasOption(FlagForSwitch(Switch::StartPaused)); |
| |
| settings.enable_checked_mode = |
| command_line.HasOption(FlagForSwitch(Switch::EnableCheckedMode)); |
| |
| settings.enable_dart_profiling = |
| command_line.HasOption(FlagForSwitch(Switch::EnableDartProfiling)); |
| |
| settings.enable_software_rendering = |
| command_line.HasOption(FlagForSwitch(Switch::EnableSoftwareRendering)); |
| |
| settings.endless_trace_buffer = |
| command_line.HasOption(FlagForSwitch(Switch::EndlessTraceBuffer)); |
| |
| settings.trace_startup = |
| command_line.HasOption(FlagForSwitch(Switch::TraceStartup)); |
| |
| settings.enable_serial_gc = |
| command_line.HasOption(FlagForSwitch(Switch::EnableSerialGC)); |
| |
| #if !FLUTTER_RELEASE |
| settings.trace_skia = true; |
| |
| if (command_line.HasOption(FlagForSwitch(Switch::TraceSkia))) { |
| // If --trace-skia is specified, then log all Skia events. |
| settings.trace_skia_allowlist.reset(); |
| } else { |
| std::string trace_skia_allowlist; |
| command_line.GetOptionValue(FlagForSwitch(Switch::TraceSkiaAllowlist), |
| &trace_skia_allowlist); |
| if (trace_skia_allowlist.size()) { |
| settings.trace_skia_allowlist = ParseCommaDelimited(trace_skia_allowlist); |
| } else { |
| settings.trace_skia_allowlist = {"skia.shaders"}; |
| } |
| } |
| #endif // !FLUTTER_RELEASE |
| |
| std::string trace_allowlist; |
| command_line.GetOptionValue(FlagForSwitch(Switch::TraceAllowlist), |
| &trace_allowlist); |
| settings.trace_allowlist = ParseCommaDelimited(trace_allowlist); |
| |
| settings.trace_systrace = |
| command_line.HasOption(FlagForSwitch(Switch::TraceSystrace)); |
| |
| settings.skia_deterministic_rendering_on_cpu = |
| command_line.HasOption(FlagForSwitch(Switch::SkiaDeterministicRendering)); |
| |
| settings.verbose_logging = |
| command_line.HasOption(FlagForSwitch(Switch::VerboseLogging)); |
| |
| command_line.GetOptionValue(FlagForSwitch(Switch::FlutterAssetsDir), |
| &settings.assets_path); |
| |
| std::vector<std::string_view> aot_shared_library_name = |
| command_line.GetOptionValues(FlagForSwitch(Switch::AotSharedLibraryName)); |
| |
| std::vector<std::string_view> vmservice_shared_library_name = |
| command_line.GetOptionValues( |
| FlagForSwitch(Switch::AotVMServiceSharedLibraryName)); |
| for (auto path : vmservice_shared_library_name) { |
| settings.vmservice_snapshot_library_path.emplace_back(path); |
| } |
| |
| std::string snapshot_asset_path; |
| command_line.GetOptionValue(FlagForSwitch(Switch::SnapshotAssetPath), |
| &snapshot_asset_path); |
| |
| std::string vm_snapshot_data_filename; |
| command_line.GetOptionValue(FlagForSwitch(Switch::VmSnapshotData), |
| &vm_snapshot_data_filename); |
| |
| command_line.GetOptionValue(FlagForSwitch(Switch::Route), &settings.route); |
| |
| std::string vm_snapshot_instr_filename; |
| command_line.GetOptionValue(FlagForSwitch(Switch::VmSnapshotInstructions), |
| &vm_snapshot_instr_filename); |
| |
| std::string isolate_snapshot_data_filename; |
| command_line.GetOptionValue(FlagForSwitch(Switch::IsolateSnapshotData), |
| &isolate_snapshot_data_filename); |
| |
| std::string isolate_snapshot_instr_filename; |
| command_line.GetOptionValue( |
| FlagForSwitch(Switch::IsolateSnapshotInstructions), |
| &isolate_snapshot_instr_filename); |
| |
| if (!aot_shared_library_name.empty()) { |
| for (std::string_view name : aot_shared_library_name) { |
| settings.application_library_path.emplace_back(name); |
| } |
| } else if (!snapshot_asset_path.empty()) { |
| settings.vm_snapshot_data_path = |
| fml::paths::JoinPaths({snapshot_asset_path, vm_snapshot_data_filename}); |
| settings.vm_snapshot_instr_path = fml::paths::JoinPaths( |
| {snapshot_asset_path, vm_snapshot_instr_filename}); |
| settings.isolate_snapshot_data_path = fml::paths::JoinPaths( |
| {snapshot_asset_path, isolate_snapshot_data_filename}); |
| settings.isolate_snapshot_instr_path = fml::paths::JoinPaths( |
| {snapshot_asset_path, isolate_snapshot_instr_filename}); |
| } |
| |
| command_line.GetOptionValue(FlagForSwitch(Switch::CacheDirPath), |
| &settings.temp_directory_path); |
| |
| bool leak_vm = "true" == command_line.GetOptionValueWithDefault( |
| FlagForSwitch(Switch::LeakVM), "true"); |
| settings.leak_vm = leak_vm; |
| |
| if (settings.icu_initialization_required) { |
| command_line.GetOptionValue(FlagForSwitch(Switch::ICUDataFilePath), |
| &settings.icu_data_path); |
| if (command_line.HasOption(FlagForSwitch(Switch::ICUSymbolPrefix))) { |
| std::string icu_symbol_prefix, native_lib_path; |
| command_line.GetOptionValue(FlagForSwitch(Switch::ICUSymbolPrefix), |
| &icu_symbol_prefix); |
| command_line.GetOptionValue(FlagForSwitch(Switch::ICUNativeLibPath), |
| &native_lib_path); |
| |
| #if FML_OS_ANDROID |
| settings.icu_mapper = GetICUStaticMapping; |
| #else |
| settings.icu_mapper = [icu_symbol_prefix, native_lib_path] { |
| return GetSymbolMapping(icu_symbol_prefix, native_lib_path); |
| }; |
| #endif |
| } |
| } |
| |
| settings.use_test_fonts = |
| command_line.HasOption(FlagForSwitch(Switch::UseTestFonts)); |
| settings.use_asset_fonts = |
| !command_line.HasOption(FlagForSwitch(Switch::DisableAssetFonts)); |
| |
| std::string enable_skparagraph = command_line.GetOptionValueWithDefault( |
| FlagForSwitch(Switch::EnableSkParagraph), ""); |
| settings.enable_skparagraph = enable_skparagraph != "false"; |
| |
| settings.enable_impeller = |
| command_line.HasOption(FlagForSwitch(Switch::EnableImpeller)); |
| |
| settings.prefetched_default_font_manager = command_line.HasOption( |
| FlagForSwitch(Switch::PrefetchedDefaultFontManager)); |
| |
| std::string all_dart_flags; |
| if (command_line.GetOptionValue(FlagForSwitch(Switch::DartFlags), |
| &all_dart_flags)) { |
| // Assume that individual flags are comma separated. |
| std::vector<std::string> flags = ParseCommaDelimited(all_dart_flags); |
| for (auto flag : flags) { |
| if (!IsAllowedDartVMFlag(flag)) { |
| FML_LOG(FATAL) << "Encountered disallowed Dart VM flag: " << flag; |
| } |
| settings.dart_flags.push_back(flag); |
| } |
| } |
| |
| #if !FLUTTER_RELEASE |
| command_line.GetOptionValue(FlagForSwitch(Switch::LogTag), &settings.log_tag); |
| #endif |
| |
| settings.dump_skp_on_shader_compilation = |
| command_line.HasOption(FlagForSwitch(Switch::DumpSkpOnShaderCompilation)); |
| |
| settings.cache_sksl = |
| command_line.HasOption(FlagForSwitch(Switch::CacheSkSL)); |
| |
| settings.purge_persistent_cache = |
| command_line.HasOption(FlagForSwitch(Switch::PurgePersistentCache)); |
| |
| if (command_line.HasOption(FlagForSwitch(Switch::OldGenHeapSize))) { |
| std::string old_gen_heap_size; |
| command_line.GetOptionValue(FlagForSwitch(Switch::OldGenHeapSize), |
| &old_gen_heap_size); |
| settings.old_gen_heap_size = std::stoi(old_gen_heap_size); |
| } |
| |
| if (command_line.HasOption( |
| FlagForSwitch(Switch::ResourceCacheMaxBytesThreshold))) { |
| std::string resource_cache_max_bytes_threshold; |
| command_line.GetOptionValue( |
| FlagForSwitch(Switch::ResourceCacheMaxBytesThreshold), |
| &resource_cache_max_bytes_threshold); |
| settings.resource_cache_max_bytes_threshold = |
| std::stoi(resource_cache_max_bytes_threshold); |
| } |
| |
| if (command_line.HasOption(FlagForSwitch(Switch::MsaaSamples))) { |
| std::string msaa_samples; |
| command_line.GetOptionValue(FlagForSwitch(Switch::MsaaSamples), |
| &msaa_samples); |
| if (msaa_samples == "0") { |
| settings.msaa_samples = 0; |
| } else if (msaa_samples == "1") { |
| settings.msaa_samples = 1; |
| } else if (msaa_samples == "2") { |
| settings.msaa_samples = 2; |
| } else if (msaa_samples == "4") { |
| settings.msaa_samples = 4; |
| } else if (msaa_samples == "8") { |
| settings.msaa_samples = 8; |
| } else if (msaa_samples == "16") { |
| settings.msaa_samples = 16; |
| } else { |
| FML_DLOG(ERROR) << "Invalid value for --msaa-samples: '" << msaa_samples |
| << "' (expected 0, 1, 2, 4, 8, or 16)."; |
| } |
| } |
| return settings; |
| } |
| |
| } // namespace flutter |