// Copyright 2014 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. import 'dart:async'; import 'package:meta/meta.dart'; import 'package:process/process.dart'; import 'application_package.dart'; import 'artifacts.dart'; import 'base/file_system.dart'; import 'base/io.dart'; import 'base/logger.dart'; import 'base/platform.dart'; import 'build_info.dart'; import 'bundle_builder.dart'; import 'desktop_device.dart'; import 'devfs.dart'; import 'device.dart'; import 'device_port_forwarder.dart'; import 'features.dart'; import 'project.dart'; import 'protocol_discovery.dart'; typedef BundleBuilderFactory = BundleBuilder Function(); BundleBuilder _defaultBundleBuilder() { return BundleBuilder(); } class PreviewDeviceDiscovery extends DeviceDiscovery { PreviewDeviceDiscovery({ required Platform platform, required Artifacts artifacts, required FileSystem fileSystem, required Logger logger, required ProcessManager processManager, required FeatureFlags featureFlags, }) : _artifacts = artifacts, _logger = logger, _processManager = processManager, _fileSystem = fileSystem, _platform = platform, _features = featureFlags; final Platform _platform; final Artifacts _artifacts; final Logger _logger; final ProcessManager _processManager; final FileSystem _fileSystem; final FeatureFlags _features; @override bool get canListAnything => _platform.isWindows; @override bool get supportsPlatform => _platform.isWindows; @override List<String> get wellKnownIds => <String>['preview']; @override Future<List<Device>> devices({ Duration? timeout, DeviceDiscoveryFilter? filter, }) async { final File previewBinary = _fileSystem.file(_artifacts.getArtifactPath(Artifact.flutterPreviewDevice)); if (!previewBinary.existsSync()) { return const <Device>[]; } final PreviewDevice device = PreviewDevice( artifacts: _artifacts, fileSystem: _fileSystem, logger: _logger, processManager: _processManager, previewBinary: previewBinary, ); final bool matchesRequirements; if (!_features.isPreviewDeviceEnabled) { matchesRequirements = false; } else if (filter == null) { matchesRequirements = true; } else { matchesRequirements = await filter.matchesRequirements(device); } return <Device>[ if (matchesRequirements) device, ]; } @override Future<List<Device>> discoverDevices({ Duration? timeout, DeviceDiscoveryFilter? filter, }) { return devices(); } } /// A device type that runs a prebuilt desktop binary alongside a locally compiled kernel file. class PreviewDevice extends Device { PreviewDevice({ required ProcessManager processManager, required Logger logger, required FileSystem fileSystem, required Artifacts artifacts, required File previewBinary, @visibleForTesting BundleBuilderFactory builderFactory = _defaultBundleBuilder, }) : _previewBinary = previewBinary, _processManager = processManager, _logger = logger, _fileSystem = fileSystem, _bundleBuilderFactory = builderFactory, _artifacts = artifacts, super('preview', ephemeral: false, category: Category.desktop, platformType: PlatformType.custom); final ProcessManager _processManager; final Logger _logger; final FileSystem _fileSystem; final BundleBuilderFactory _bundleBuilderFactory; final Artifacts _artifacts; final File _previewBinary; /// The set of plugins that are allowed to be used by Preview users. /// /// Currently no plugins are supported. static const List<String> supportedPubPlugins = <String>[]; @override void clearLogs() { } @override Future<void> dispose() async { } @override Future<String?> get emulatorId async => null; final DesktopLogReader _logReader = DesktopLogReader(); @override FutureOr<DeviceLogReader> getLogReader({ApplicationPackage? app, bool includePastLogs = false}) => _logReader; @override Future<bool> installApp(ApplicationPackage? app, {String? userIdentifier}) async => true; @override Future<bool> isAppInstalled(ApplicationPackage app, {String? userIdentifier}) async => false; @override Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false; @override Future<bool> get isLocalEmulator async => false; @override bool isSupported() => true; @override bool isSupportedForProject(FlutterProject flutterProject) => true; @override String get name => 'preview'; @override DevicePortForwarder get portForwarder => const NoOpDevicePortForwarder(); @override Future<String> get sdkNameAndVersion async => 'preview'; Process? _process; @override Future<LaunchResult> startApp(ApplicationPackage? package, { String? mainPath, String? route, required DebuggingOptions debuggingOptions, Map<String, dynamic> platformArgs = const <String, dynamic>{}, bool prebuiltApplication = false, bool ipv6 = false, String? userIdentifier, }) async { final Directory assetDirectory = _fileSystem.systemTempDirectory .createTempSync('flutter_preview.'); // Build assets and perform initial compilation. Status? status; try { status = _logger.startProgress('Compiling application for preview...'); await _bundleBuilderFactory().build( buildInfo: debuggingOptions.buildInfo, mainPath: mainPath, platform: TargetPlatform.windows_x64, assetDirPath: getAssetBuildDirectory(), ); copyDirectory(_fileSystem.directory( getAssetBuildDirectory()), assetDirectory.childDirectory('data').childDirectory('flutter_assets'), ); } finally { status?.stop(); } // Merge with precompiled executable. final String copiedPreviewBinaryPath = assetDirectory.childFile(_previewBinary.basename).path; _previewBinary.copySync(copiedPreviewBinaryPath); final String windowsPath = _artifacts .getArtifactPath(Artifact.windowsDesktopPath, platform: TargetPlatform.windows_x64, mode: BuildMode.debug); final File windowsDll = _fileSystem.file(_fileSystem.path.join(windowsPath, 'flutter_windows.dll')); final File icu = _fileSystem.file(_fileSystem.path.join(windowsPath, 'icudtl.dat')); windowsDll.copySync(assetDirectory.childFile('flutter_windows.dll').path); icu.copySync(assetDirectory.childDirectory('data').childFile('icudtl.dat').path); final Process process = await _processManager.start( <String>[copiedPreviewBinaryPath], ); _process = process; _logReader.initializeProcess(process); final ProtocolDiscovery vmServiceDiscovery = ProtocolDiscovery.vmService(_logReader, devicePort: debuggingOptions.deviceVmServicePort, hostPort: debuggingOptions.hostVmServicePort, ipv6: ipv6, logger: _logger, ); try { final Uri? vmServiceUri = await vmServiceDiscovery.uri; if (vmServiceUri != null) { return LaunchResult.succeeded(vmServiceUri: vmServiceUri); } _logger.printError( 'Error waiting for a debug connection: ' 'The log reader stopped unexpectedly.', ); } on Exception catch (error) { _logger.printError('Error waiting for a debug connection: $error'); } finally { await vmServiceDiscovery.cancel(); } return LaunchResult.failed(); } @override Future<bool> stopApp(ApplicationPackage? app, {String? userIdentifier}) async { return _process?.kill() ?? false; } @override Future<TargetPlatform> get targetPlatform async { return TargetPlatform.windows_x64; } @override Future<bool> uninstallApp(ApplicationPackage app, {String? userIdentifier}) async { return true; } @override DevFSWriter createDevFSWriter(ApplicationPackage? app, String? userIdentifier) { return LocalDevFSWriter(fileSystem: _fileSystem); } }