desktop_device.dart 10.4 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
// @dart = 2.8

7 8 9
import 'dart:async';

import 'package:meta/meta.dart';
10
import 'package:process/process.dart';
11 12 13

import 'application_package.dart';
import 'base/common.dart';
14
import 'base/file_system.dart';
15
import 'base/io.dart';
16
import 'base/logger.dart';
17
import 'base/os.dart';
18 19
import 'build_info.dart';
import 'convert.dart';
20
import 'devfs.dart';
21
import 'device.dart';
22
import 'device_port_forwarder.dart';
23 24 25 26 27
import 'protocol_discovery.dart';

/// A partial implementation of Device for desktop-class devices to inherit
/// from, containing implementations that are common to all desktop devices.
abstract class DesktopDevice extends Device {
28 29 30
  DesktopDevice(String identifier, {
      @required PlatformType platformType,
      @required bool ephemeral,
31 32 33 34 35 36 37 38
      @required Logger logger,
      @required ProcessManager processManager,
      @required FileSystem fileSystem,
      @required OperatingSystemUtils operatingSystemUtils,
    }) : _logger = logger,
         _processManager = processManager,
         _fileSystem = fileSystem,
         _operatingSystemUtils = operatingSystemUtils,
39 40 41 42 43 44 45 46 47
         super(
          identifier,
          category: Category.desktop,
          platformType: platformType,
          ephemeral: ephemeral,
        );

  final Logger _logger;
  final ProcessManager _processManager;
48
  final FileSystem _fileSystem;
49
  final OperatingSystemUtils _operatingSystemUtils;
50 51 52
  final Set<Process> _runningProcesses = <Process>{};
  final DesktopLogReader _deviceLogReader = DesktopLogReader();

53 54 55 56
  @override
  DevFSWriter createDevFSWriter(covariant ApplicationPackage app, String userIdentifier) {
    return LocalDevFSWriter(fileSystem: _fileSystem);
  }
57

58 59 60
  // Since the host and target devices are the same, no work needs to be done
  // to install the application.
  @override
61 62 63 64
  Future<bool> isAppInstalled(
    ApplicationPackage app, {
    String userIdentifier,
  }) async => true;
65 66 67 68 69 70 71 72 73

  // Since the host and target devices are the same, no work needs to be done
  // to install the application.
  @override
  Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => true;

  // Since the host and target devices are the same, no work needs to be done
  // to install the application.
  @override
74 75 76 77
  Future<bool> installApp(
    ApplicationPackage app, {
    String userIdentifier,
  }) async => true;
78 79 80 81

  // Since the host and target devices are the same, no work needs to be done
  // to uninstall the application.
  @override
82 83 84 85
  Future<bool> uninstallApp(
    ApplicationPackage app, {
    String userIdentifier,
  }) async => true;
86 87 88 89 90 91 92 93 94 95 96

  @override
  Future<bool> get isLocalEmulator async => false;

  @override
  Future<String> get emulatorId async => null;

  @override
  DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder();

  @override
97
  Future<String> get sdkNameAndVersion async => _operatingSystemUtils.name;
98

99 100 101
  @override
  bool supportsRuntimeMode(BuildMode buildMode) => buildMode != BuildMode.jitRelease;

102
  @override
103 104 105 106 107
  DeviceLogReader getLogReader({
    ApplicationPackage app,
    bool includePastLogs = false,
  }) {
    assert(!includePastLogs, 'Past log reading not supported on desktop.');
108 109 110 111 112 113 114 115 116 117 118
    return _deviceLogReader;
  }

  @override
  void clearLogs() {}

  @override
  Future<LaunchResult> startApp(
    ApplicationPackage package, {
    String mainPath,
    String route,
119 120
    @required DebuggingOptions debuggingOptions,
    Map<String, dynamic> platformArgs = const <String, dynamic>{},
121 122
    bool prebuiltApplication = false,
    bool ipv6 = false,
123
    String userIdentifier,
124 125 126 127
  }) async {
    if (!prebuiltApplication) {
      await buildForDevice(
        package,
128
        buildInfo: debuggingOptions.buildInfo,
129 130 131 132 133 134
        mainPath: mainPath,
      );
    }

    // Ensure that the executable is locatable.
    final BuildMode buildMode = debuggingOptions?.buildInfo?.mode;
135
    final bool traceStartup = platformArgs['trace-startup'] as bool ?? false;
136 137
    final String executable = executablePathForDevice(package, buildMode);
    if (executable == null) {
138
      _logger.printError('Unable to find executable to run');
139 140 141
      return LaunchResult.failed();
    }

142 143 144
    final Process process = await _processManager.start(
      <String>[
        executable,
145
        ...?debuggingOptions?.dartEntrypointArgs,
146
      ],
147
      environment: _computeEnvironment(debuggingOptions, traceStartup, route),
148
    );
149 150 151
    _runningProcesses.add(process);
    unawaited(process.exitCode.then((_) => _runningProcesses.remove(process)));

152
    _deviceLogReader.initializeProcess(process);
153 154 155
    if (debuggingOptions?.buildInfo?.isRelease == true) {
      return LaunchResult.succeeded();
    }
156 157
    final ProtocolDiscovery observatoryDiscovery = ProtocolDiscovery.observatory(_deviceLogReader,
      devicePort: debuggingOptions?.deviceVmServicePort,
158
      hostPort: debuggingOptions?.hostVmServicePort,
159
      ipv6: ipv6,
160
      logger: _logger,
161
    );
162 163
    try {
      final Uri observatoryUri = await observatoryDiscovery.uri;
164 165 166 167
      if (observatoryUri != null) {
        onAttached(package, buildMode, process);
        return LaunchResult.succeeded(observatoryUri: observatoryUri);
      }
168
      _logger.printError(
169 170 171
        'Error waiting for a debug connection: '
        'The log reader stopped unexpectedly.',
      );
172
    } on Exception catch (error) {
173
      _logger.printError('Error waiting for a debug connection: $error');
174 175 176
    } finally {
      await observatoryDiscovery.cancel();
    }
177
    return LaunchResult.failed();
178 179 180
  }

  @override
181 182 183 184
  Future<bool> stopApp(
    ApplicationPackage app, {
    String userIdentifier,
  }) async {
185 186 187
    bool succeeded = true;
    // Walk a copy of _runningProcesses, since the exit handler removes from the
    // set.
188
    for (final Process process in Set<Process>.of(_runningProcesses)) {
189
      succeeded &= _processManager.killPid(process.pid);
190 191 192 193
    }
    return succeeded;
  }

194 195 196 197 198
  @override
  Future<void> dispose() async {
    await portForwarder?.dispose();
  }

199 200 201 202 203 204 205 206 207 208 209 210 211 212
  /// Builds the current project for this device, with the given options.
  Future<void> buildForDevice(
    ApplicationPackage package, {
    String mainPath,
    BuildInfo buildInfo,
  });

  /// Returns the path to the executable to run for [package] on this device for
  /// the given [buildMode].
  String executablePathForDevice(ApplicationPackage package, BuildMode buildMode);

  /// Called after a process is attached, allowing any device-specific extra
  /// steps to be run.
  void onAttached(ApplicationPackage package, BuildMode buildMode, Process process) {}
213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253

  /// 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_<N> (indexing from 1) to the individual switches.
  Map<String, String> _computeEnvironment(DebuggingOptions debuggingOptions, bool traceStartup, String route) {
    int flags = 0;
    final Map<String, String> environment = <String, String>{};

    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}');
    }
254 255 256
    if (debuggingOptions.traceSkiaAllowlist != null) {
      addFlag('trace-skia-allowlist=${debuggingOptions.traceSkiaAllowlist}');
    }
257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301
    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;
  }
302 303
}

304 305
/// A log reader for desktop applications that delegates to a [Process] stdout
/// and stderr streams.
306 307 308
class DesktopLogReader extends DeviceLogReader {
  final StreamController<List<int>> _inputController = StreamController<List<int>>.broadcast();

309
  /// Begin listening to the stdout and stderr streams of the provided [process].
310 311 312
  void initializeProcess(Process process) {
    process.stdout.listen(_inputController.add);
    process.stderr.listen(_inputController.add);
313
    process.exitCode.whenComplete(_inputController.close);
314 315 316 317 318 319 320 321 322 323 324
  }

  @override
  Stream<String> get logLines {
    return _inputController.stream
      .transform(utf8.decoder)
      .transform(const LineSplitter());
  }

  @override
  String get name => 'desktop';
325 326 327 328 329

  @override
  void dispose() {
    // Nothing to dispose.
  }
330
}