
Fixes https://github.com/flutter/flutter/issues/130277 This PR does two things: 1. introduce a hidden `flutter build _preview` command, that will build a debug windows desktop app and copy it into the SDK's binary cache. This command is only intended to be run during packaging. 2. introduce a new device type, called `PreviewDevice`, which relies on the prebuilt desktop debug app from step 1, copies it into the target app's assets build folder, and then hot reloads their dart code into it.
260 lines
7.7 KiB
Dart
260 lines
7.7 KiB
Dart
// 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;
|
|
|
|
@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);
|
|
}
|
|
}
|