diff --git a/packages/flutter_tools/lib/src/desktop_device.dart b/packages/flutter_tools/lib/src/desktop_device.dart index 3fa0d1b7f04..917a6e08f1f 100644 --- a/packages/flutter_tools/lib/src/desktop_device.dart +++ b/packages/flutter_tools/lib/src/desktop_device.dart @@ -111,8 +111,8 @@ abstract class DesktopDevice extends Device { ApplicationPackage package, { String mainPath, String route, - DebuggingOptions debuggingOptions, - Map platformArgs, + @required DebuggingOptions debuggingOptions, + Map platformArgs = const {}, bool prebuiltApplication = false, bool ipv6 = false, String userIdentifier, @@ -120,13 +120,14 @@ abstract class DesktopDevice extends Device { if (!prebuiltApplication) { await buildForDevice( package, - buildInfo: debuggingOptions?.buildInfo, + buildInfo: debuggingOptions.buildInfo, mainPath: mainPath, ); } // Ensure that the executable is locatable. final BuildMode buildMode = debuggingOptions?.buildInfo?.mode; + final bool traceStartup = platformArgs['trace-startup'] as bool ?? false; final String executable = executablePathForDevice(package, buildMode); if (executable == null) { _logger.printError('Unable to find executable to run'); @@ -137,6 +138,7 @@ abstract class DesktopDevice extends Device { [ executable, ], + environment: _computeEnvironment(debuggingOptions, traceStartup, route), ); _runningProcesses.add(process); unawaited(process.exitCode.then((_) => _runningProcesses.remove(process))); @@ -202,6 +204,92 @@ abstract class DesktopDevice extends Device { /// Called after a process is attached, allowing any device-specific extra /// steps to be run. void onAttached(ApplicationPackage package, BuildMode buildMode, Process process) {} + + /// Computes a set of environment variables used to pass debugging information + /// to the engine without interfering with application level command line + /// arguments. + /// + /// The format of the environment variables is: + /// * FLUTTER_ENGINE_SWITCHES to the number of switches. + /// * FLUTTER_ENGINE_SWITCH_ (indexing from 1) to the individual switches. + Map _computeEnvironment(DebuggingOptions debuggingOptions, bool traceStartup, String route) { + int flags = 0; + final Map environment = {}; + + void addFlag(String value) { + flags += 1; + environment['FLUTTER_ENGINE_SWITCH_$flags'] = value; + } + void finish() { + environment['FLUTTER_ENGINE_SWITCHES'] = flags.toString(); + } + + addFlag('enable-dart-profiling=true'); + addFlag('enable-background-compilation=true'); + + if (traceStartup) { + addFlag('trace-startup=true'); + } + if (route != null) { + addFlag('route=$route'); + } + if (debuggingOptions.enableSoftwareRendering) { + addFlag('enable-software-rendering=true'); + } + if (debuggingOptions.skiaDeterministicRendering) { + addFlag('skia-deterministic-rendering=true'); + } + if (debuggingOptions.traceSkia) { + addFlag('trace-skia=true'); + } + if (debuggingOptions.traceAllowlist != null) { + addFlag('trace-allowlist=${debuggingOptions.traceAllowlist}'); + } + if (debuggingOptions.traceSystrace) { + addFlag('trace-systrace=true'); + } + if (debuggingOptions.endlessTraceBuffer) { + addFlag('endless-trace-buffer=true'); + } + if (debuggingOptions.dumpSkpOnShaderCompilation) { + addFlag('dump-skp-on-shader-compilation=true'); + } + if (debuggingOptions.cacheSkSL) { + addFlag('cache-sksl=true'); + } + if (debuggingOptions.purgePersistentCache) { + addFlag('purge-persistent-cache=true'); + } + // Options only supported when there is a VM Service connection between the + // tool and the device, usually in debug or profile mode. + if (debuggingOptions.debuggingEnabled) { + if (debuggingOptions.deviceVmServicePort != null) { + addFlag('observatory-port=${debuggingOptions.deviceVmServicePort}'); + } + if (debuggingOptions.buildInfo.isDebug) { + addFlag('enable-checked-mode=true'); + addFlag('verify-entry-points=true'); + } + if (debuggingOptions.startPaused) { + addFlag('start-paused=true'); + } + if (debuggingOptions.disableServiceAuthCodes) { + addFlag('disable-service-auth-codes=true'); + } + final String dartVmFlags = computeDartVmFlags(debuggingOptions); + if (dartVmFlags.isNotEmpty) { + addFlag('dart-flags=$dartVmFlags'); + } + if (debuggingOptions.useTestFonts) { + addFlag('use-test-fonts=true'); + } + if (debuggingOptions.verboseSystemLogs) { + addFlag('verbose-logging=true'); + } + } + finish(); + return environment; + } } /// A log reader for desktop applications that delegates to a [Process] stdout diff --git a/packages/flutter_tools/test/general.shard/desktop_device_test.dart b/packages/flutter_tools/test/general.shard/desktop_device_test.dart index a7f3cf393bc..319bc7929d3 100644 --- a/packages/flutter_tools/test/general.shard/desktop_device_test.dart +++ b/packages/flutter_tools/test/general.shard/desktop_device_test.dart @@ -80,7 +80,7 @@ void main() { final Completer completer = Completer(); final FakeProcessManager processManager = FakeProcessManager.list([ FakeCommand( - command: const ['null'], + command: const ['debug'], stdout: 'Observatory listening on http://127.0.0.1/0\n', completer: completer, ), @@ -89,7 +89,11 @@ void main() { final String executableName = device.executablePathForDevice(null, BuildMode.debug); fileSystem.file(executableName).writeAsStringSync('\n'); final FakeAppplicationPackage package = FakeAppplicationPackage(); - final LaunchResult result = await device.startApp(package, prebuiltApplication: true); + final LaunchResult result = await device.startApp( + package, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + ); expect(result.started, true); expect(result.observatoryUri, Uri.parse('http://127.0.0.1/0')); @@ -99,7 +103,11 @@ void main() { final BufferLogger logger = BufferLogger.test(); final DesktopDevice device = setUpDesktopDevice(nullExecutablePathForDevice: true, logger: logger); final FakeAppplicationPackage package = FakeAppplicationPackage(); - final LaunchResult result = await device.startApp(package, prebuiltApplication: true); + final LaunchResult result = await device.startApp( + package, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + ); expect(result.started, false); expect(logger.errorText, contains('Unable to find executable to run')); @@ -109,20 +117,123 @@ void main() { final Completer completer = Completer(); final FakeProcessManager processManager = FakeProcessManager.list([ FakeCommand( - command: const ['null'], + command: const ['debug'], stdout: 'Observatory listening on http://127.0.0.1/0\n', completer: completer, ), ]); final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); final FakeAppplicationPackage package = FakeAppplicationPackage(); - final LaunchResult result = await device.startApp(package, prebuiltApplication: true); + final LaunchResult result = await device.startApp( + package, + prebuiltApplication: true, + debuggingOptions: DebuggingOptions.enabled(BuildInfo.debug), + ); expect(result.started, true); expect(await device.stopApp(package), true); }); }); + testWithoutContext('startApp supports DebuggingOptions through FLUTTER_ENGINE_SWITCH environment variables', () async { + final Completer completer = Completer(); + final FakeProcessManager processManager = FakeProcessManager.list([ + FakeCommand( + command: const ['debug'], + stdout: 'Observatory listening on http://127.0.0.1/0\n', + completer: completer, + environment: const { + 'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true', + 'FLUTTER_ENGINE_SWITCH_2': 'enable-background-compilation=true', + 'FLUTTER_ENGINE_SWITCH_3': 'trace-startup=true', + 'FLUTTER_ENGINE_SWITCH_4': 'enable-software-rendering=true', + 'FLUTTER_ENGINE_SWITCH_5': 'skia-deterministic-rendering=true', + 'FLUTTER_ENGINE_SWITCH_6': 'trace-skia=true', + 'FLUTTER_ENGINE_SWITCH_7': 'trace-allowlist=foo,bar', + 'FLUTTER_ENGINE_SWITCH_8': 'trace-systrace=true', + 'FLUTTER_ENGINE_SWITCH_9': 'endless-trace-buffer=true', + 'FLUTTER_ENGINE_SWITCH_10': 'dump-skp-on-shader-compilation=true', + 'FLUTTER_ENGINE_SWITCH_11': 'cache-sksl=true', + 'FLUTTER_ENGINE_SWITCH_12': 'purge-persistent-cache=true', + 'FLUTTER_ENGINE_SWITCH_13': 'enable-checked-mode=true', + 'FLUTTER_ENGINE_SWITCH_14': 'verify-entry-points=true', + 'FLUTTER_ENGINE_SWITCH_15': 'start-paused=true', + 'FLUTTER_ENGINE_SWITCH_16': 'disable-service-auth-codes=true', + 'FLUTTER_ENGINE_SWITCH_17': 'dart-flags=--null_assertions', + 'FLUTTER_ENGINE_SWITCH_18': 'use-test-fonts=true', + 'FLUTTER_ENGINE_SWITCH_19': 'verbose-logging=true', + 'FLUTTER_ENGINE_SWITCHES': '19' + } + ), + ]); + final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); + final FakeAppplicationPackage package = FakeAppplicationPackage(); + final LaunchResult result = await device.startApp( + package, + prebuiltApplication: true, + platformArgs: { + 'trace-startup': true, + }, + debuggingOptions: DebuggingOptions.enabled( + BuildInfo.debug, + startPaused: true, + disableServiceAuthCodes: true, + dartFlags: '', + enableSoftwareRendering: true, + skiaDeterministicRendering: true, + traceSkia: true, + traceAllowlist: 'foo,bar', + traceSystrace: true, + endlessTraceBuffer: true, + dumpSkpOnShaderCompilation: true, + cacheSkSL: true, + purgePersistentCache: true, + useTestFonts: true, + verboseSystemLogs: true, + initializePlatform: true, + nullAssertions: true, + ), + ); + + expect(result.started, true); + }); + + testWithoutContext('startApp supports DebuggingOptions through FLUTTER_ENGINE_SWITCH environment variables when debugging is disabled', () async { + final Completer completer = Completer(); + final FakeProcessManager processManager = FakeProcessManager.list([ + FakeCommand( + command: const ['debug'], + stdout: 'Observatory listening on http://127.0.0.1/0\n', + completer: completer, + environment: const { + 'FLUTTER_ENGINE_SWITCH_1': 'enable-dart-profiling=true', + 'FLUTTER_ENGINE_SWITCH_2': 'enable-background-compilation=true', + 'FLUTTER_ENGINE_SWITCH_3': 'trace-startup=true', + 'FLUTTER_ENGINE_SWITCH_4': 'trace-allowlist=foo,bar', + 'FLUTTER_ENGINE_SWITCH_5': 'cache-sksl=true', + 'FLUTTER_ENGINE_SWITCHES': '5' + } + ), + ]); + final FakeDesktopDevice device = setUpDesktopDevice(processManager: processManager); + final FakeAppplicationPackage package = FakeAppplicationPackage(); + final LaunchResult result = await device.startApp( + package, + prebuiltApplication: true, + platformArgs: { + 'trace-startup': true, + }, + debuggingOptions: DebuggingOptions.disabled( + BuildInfo.debug, + traceAllowlist: 'foo,bar', + cacheSkSL: true, + initializePlatform: true, + ), + ); + + expect(result.started, true); + }); + testWithoutContext('Port forwarder is a no-op', () async { final FakeDesktopDevice device = setUpDesktopDevice(); final DevicePortForwarder portForwarder = device.portForwarder;