Delete packages/flutter_tools/lib/src/fuchsia directory (#154880)
It's not being actively used, and fuchsia team does not have bandwidth to maintain it. Bug: https://b.corp.google.com/issues/353729557 ## Pre-launch Checklist - [x] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [x] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [x] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [x] I signed the [CLA]. - [x] I listed at least one issue that this PR fixes in the description above. - [x] I updated/added relevant documentation (doc comments with `///`). - [x] I added new tests to check the change I am making, or this PR is [test-exempt]. - [x] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [x] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
1db9a61348
commit
a4f45471bc
@ -41,9 +41,6 @@ import 'flutter_application_package.dart';
|
||||
import 'flutter_cache.dart';
|
||||
import 'flutter_device_manager.dart';
|
||||
import 'flutter_features.dart';
|
||||
import 'fuchsia/fuchsia_device.dart' show FuchsiaDeviceTools;
|
||||
import 'fuchsia/fuchsia_sdk.dart' show FuchsiaArtifacts, FuchsiaSdk;
|
||||
import 'fuchsia/fuchsia_workflow.dart' show FuchsiaWorkflow, fuchsiaWorkflow;
|
||||
import 'globals.dart' as globals;
|
||||
import 'ios/ios_workflow.dart';
|
||||
import 'ios/iproxy.dart';
|
||||
@ -203,7 +200,6 @@ Future<T> runInContext<T>(
|
||||
artifacts: globals.artifacts!,
|
||||
flutterVersion: globals.flutterVersion,
|
||||
androidWorkflow: androidWorkflow!,
|
||||
fuchsiaWorkflow: fuchsiaWorkflow!,
|
||||
xcDevice: globals.xcdevice!,
|
||||
userMessages: globals.userMessages,
|
||||
windowsWorkflow: windowsWorkflow!,
|
||||
@ -211,7 +207,6 @@ Future<T> runInContext<T>(
|
||||
platform: globals.platform,
|
||||
featureFlags: featureFlags,
|
||||
),
|
||||
fuchsiaSdk: globals.fuchsiaSdk!,
|
||||
operatingSystemUtils: globals.os,
|
||||
customDevicesConfig: globals.customDevicesConfig,
|
||||
nativeAssetsBuilder: globals.nativeAssetsBuilder,
|
||||
@ -244,14 +239,6 @@ Future<T> runInContext<T>(
|
||||
fs: globals.fs,
|
||||
flutterRoot: Cache.flutterRoot!,
|
||||
),
|
||||
FuchsiaArtifacts: () => FuchsiaArtifacts.find(),
|
||||
FuchsiaDeviceTools: () => FuchsiaDeviceTools(),
|
||||
FuchsiaSdk: () => FuchsiaSdk(),
|
||||
FuchsiaWorkflow: () => FuchsiaWorkflow(
|
||||
featureFlags: featureFlags,
|
||||
platform: globals.platform,
|
||||
fuchsiaArtifacts: globals.fuchsiaArtifacts!,
|
||||
),
|
||||
GradleUtils: () => GradleUtils(
|
||||
operatingSystemUtils: globals.os,
|
||||
logger: globals.logger,
|
||||
|
@ -28,7 +28,6 @@ import 'custom_devices/custom_device_workflow.dart';
|
||||
import 'device.dart';
|
||||
import 'doctor_validator.dart';
|
||||
import 'features.dart';
|
||||
import 'fuchsia/fuchsia_workflow.dart';
|
||||
import 'globals.dart' as globals;
|
||||
import 'http_host_validator.dart';
|
||||
import 'intellij/intellij_validator.dart';
|
||||
@ -199,7 +198,6 @@ class _DefaultDoctorValidatorsProvider implements DoctorValidatorsProvider {
|
||||
return _workflows ??= <Workflow>[
|
||||
if (globals.iosWorkflow!.appliesToHostPlatform) globals.iosWorkflow!,
|
||||
if (androidWorkflow?.appliesToHostPlatform ?? false) androidWorkflow!,
|
||||
if (fuchsiaWorkflow?.appliesToHostPlatform ?? false) fuchsiaWorkflow!,
|
||||
if (linuxWorkflow.appliesToHostPlatform) linuxWorkflow,
|
||||
if (macOSWorkflow.appliesToHostPlatform) macOSWorkflow,
|
||||
if (windowsWorkflow?.appliesToHostPlatform ?? false) windowsWorkflow!,
|
||||
|
@ -12,7 +12,6 @@ import 'base/logger.dart';
|
||||
import 'base/process.dart';
|
||||
import 'base/user_messages.dart';
|
||||
import 'build_info.dart';
|
||||
import 'fuchsia/application_package.dart';
|
||||
import 'globals.dart' as globals;
|
||||
import 'ios/application_package.dart';
|
||||
import 'linux/application_package.dart';
|
||||
@ -104,9 +103,8 @@ class FlutterApplicationPackageFactory extends ApplicationPackageFactory {
|
||||
: WindowsApp.fromPrebuiltApp(applicationBinary);
|
||||
case TargetPlatform.fuchsia_arm64:
|
||||
case TargetPlatform.fuchsia_x64:
|
||||
return applicationBinary == null
|
||||
? FuchsiaApp.fromFuchsiaProject(FlutterProject.current().fuchsia)
|
||||
: FuchsiaApp.fromPrebuiltApp(applicationBinary);
|
||||
// Unsupported yet.
|
||||
throw UnimplementedError();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,9 +16,6 @@ import 'custom_devices/custom_device.dart';
|
||||
import 'custom_devices/custom_devices_config.dart';
|
||||
import 'device.dart';
|
||||
import 'features.dart';
|
||||
import 'fuchsia/fuchsia_device.dart';
|
||||
import 'fuchsia/fuchsia_sdk.dart';
|
||||
import 'fuchsia/fuchsia_workflow.dart';
|
||||
import 'ios/devices.dart';
|
||||
import 'ios/ios_workflow.dart';
|
||||
import 'ios/simulators.dart';
|
||||
@ -48,11 +45,9 @@ class FlutterDeviceManager extends DeviceManager {
|
||||
required XCDevice xcDevice,
|
||||
required AndroidWorkflow androidWorkflow,
|
||||
required IOSWorkflow iosWorkflow,
|
||||
required FuchsiaWorkflow fuchsiaWorkflow,
|
||||
required FlutterVersion flutterVersion,
|
||||
required Artifacts artifacts,
|
||||
required MacOSWorkflow macOSWorkflow,
|
||||
required FuchsiaSdk fuchsiaSdk,
|
||||
required UserMessages userMessages,
|
||||
required OperatingSystemUtils operatingSystemUtils,
|
||||
required WindowsWorkflow windowsWorkflow,
|
||||
@ -77,12 +72,6 @@ class FlutterDeviceManager extends DeviceManager {
|
||||
IOSSimulators(
|
||||
iosSimulatorUtils: iosSimulatorUtils,
|
||||
),
|
||||
FuchsiaDevices(
|
||||
fuchsiaSdk: fuchsiaSdk,
|
||||
logger: logger,
|
||||
fuchsiaWorkflow: fuchsiaWorkflow,
|
||||
platform: platform,
|
||||
),
|
||||
FlutterTesterDevices(
|
||||
fileSystem: fileSystem,
|
||||
flutterVersion: flutterVersion,
|
||||
|
@ -1,80 +0,0 @@
|
||||
// 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 '../application_package.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../build_info.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../project.dart';
|
||||
|
||||
abstract class FuchsiaApp extends ApplicationPackage {
|
||||
FuchsiaApp({required String projectBundleId}) : super(id: projectBundleId);
|
||||
|
||||
/// Creates a new [FuchsiaApp] from a fuchsia sub project.
|
||||
static FuchsiaApp? fromFuchsiaProject(FuchsiaProject project) {
|
||||
if (!project.existsSync()) {
|
||||
// If the project doesn't exist at all the current hint to run flutter
|
||||
// create is accurate.
|
||||
return null;
|
||||
}
|
||||
return BuildableFuchsiaApp(
|
||||
project: project,
|
||||
);
|
||||
}
|
||||
|
||||
/// Creates a new [FuchsiaApp] from an existing .far archive.
|
||||
///
|
||||
/// [applicationBinary] is the path to the .far archive.
|
||||
static FuchsiaApp? fromPrebuiltApp(FileSystemEntity applicationBinary) {
|
||||
final FileSystemEntityType entityType = globals.fs.typeSync(applicationBinary.path);
|
||||
if (entityType != FileSystemEntityType.file) {
|
||||
globals.printError('File "${applicationBinary.path}" does not exist or is not a .far file. Use far archive.');
|
||||
return null;
|
||||
}
|
||||
return PrebuiltFuchsiaApp(
|
||||
applicationPackage: applicationBinary,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String get displayName => id;
|
||||
|
||||
/// The location of the 'far' archive containing the built app.
|
||||
File farArchive(BuildMode buildMode);
|
||||
}
|
||||
|
||||
class PrebuiltFuchsiaApp extends FuchsiaApp implements PrebuiltApplicationPackage {
|
||||
PrebuiltFuchsiaApp({
|
||||
required this.applicationPackage,
|
||||
}) : // TODO(zanderso): Extract the archive and extract the id from meta/package.
|
||||
super(projectBundleId: applicationPackage.path);
|
||||
|
||||
@override
|
||||
File farArchive(BuildMode buildMode) => globals.fs.file(applicationPackage);
|
||||
|
||||
@override
|
||||
String get name => applicationPackage.path;
|
||||
|
||||
@override
|
||||
final FileSystemEntity applicationPackage;
|
||||
}
|
||||
|
||||
class BuildableFuchsiaApp extends FuchsiaApp {
|
||||
BuildableFuchsiaApp({required this.project}) :
|
||||
super(projectBundleId: project.project.manifest.appName);
|
||||
|
||||
final FuchsiaProject project;
|
||||
|
||||
@override
|
||||
File farArchive(BuildMode buildMode) {
|
||||
// TODO(zanderso): Distinguish among build modes.
|
||||
final String outDir = getFuchsiaBuildDirectory();
|
||||
final String pkgDir = globals.fs.path.join(outDir, 'pkg');
|
||||
final String appName = project.project.manifest.appName;
|
||||
return globals.fs.file(globals.fs.path.join(pkgDir, '$appName-0.far'));
|
||||
}
|
||||
|
||||
@override
|
||||
String get name => project.project.manifest.appName;
|
||||
}
|
@ -1,956 +0,0 @@
|
||||
// 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:vm_service/vm_service.dart' as vm_service;
|
||||
|
||||
import '../application_package.dart';
|
||||
import '../artifacts.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/net.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/time.dart';
|
||||
import '../build_info.dart';
|
||||
import '../device.dart';
|
||||
import '../device_port_forwarder.dart';
|
||||
import '../device_vm_service_discovery_for_attach.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../project.dart';
|
||||
import '../runner/flutter_command.dart';
|
||||
import '../vmservice.dart';
|
||||
|
||||
import 'application_package.dart';
|
||||
import 'fuchsia_ffx.dart';
|
||||
import 'fuchsia_pm.dart';
|
||||
import 'fuchsia_sdk.dart';
|
||||
import 'fuchsia_workflow.dart';
|
||||
import 'pkgctl.dart';
|
||||
|
||||
/// The [FuchsiaDeviceTools] instance.
|
||||
FuchsiaDeviceTools get fuchsiaDeviceTools => context.get<FuchsiaDeviceTools>()!;
|
||||
|
||||
/// Fuchsia device-side tools.
|
||||
class FuchsiaDeviceTools {
|
||||
late final FuchsiaPkgctl pkgctl = FuchsiaPkgctl();
|
||||
late final FuchsiaFfx ffx = FuchsiaFfx();
|
||||
}
|
||||
|
||||
final String _ipv4Loopback = InternetAddress.loopbackIPv4.address;
|
||||
final String _ipv6Loopback = InternetAddress.loopbackIPv6.address;
|
||||
|
||||
// Enables testing the fuchsia isolate discovery
|
||||
Future<FlutterVmService> _kDefaultFuchsiaIsolateDiscoveryConnector(Uri uri) {
|
||||
return connectToVmService(uri, logger: globals.logger);
|
||||
}
|
||||
|
||||
Future<void> _kDefaultDartDevelopmentServiceStarter(
|
||||
Device device,
|
||||
Uri vmServiceUri,
|
||||
bool disableServiceAuthCodes,
|
||||
) async {
|
||||
await device.dds.startDartDevelopmentService(
|
||||
vmServiceUri,
|
||||
ddsPort: 0,
|
||||
disableServiceAuthCodes: disableServiceAuthCodes,
|
||||
ipv6: true,
|
||||
);
|
||||
}
|
||||
|
||||
/// Read the log for a particular device.
|
||||
class _FuchsiaLogReader extends DeviceLogReader {
|
||||
_FuchsiaLogReader(this._device, this._systemClock, [this._app]);
|
||||
|
||||
// \S matches non-whitespace characters.
|
||||
static final RegExp _flutterLogOutput = RegExp(r'INFO: \S+\(flutter\): ');
|
||||
|
||||
final FuchsiaDevice _device;
|
||||
final ApplicationPackage? _app;
|
||||
final SystemClock _systemClock;
|
||||
|
||||
@override
|
||||
String get name => _device.name;
|
||||
|
||||
Stream<String>? _logLines;
|
||||
@override
|
||||
Stream<String> get logLines {
|
||||
final Stream<String>? logStream = globals.fuchsiaSdk?.syslogs(_device.id);
|
||||
_logLines ??= _processLogs(logStream);
|
||||
return _logLines ?? const Stream<String>.empty();
|
||||
}
|
||||
|
||||
Stream<String>? _processLogs(Stream<String>? lines) {
|
||||
if (lines == null) {
|
||||
return null;
|
||||
}
|
||||
// Get the starting time of the log processor to filter logs from before
|
||||
// the process attached.
|
||||
final DateTime startTime = _systemClock.now();
|
||||
// Determine if line comes from flutter, and optionally whether it matches
|
||||
// the correct fuchsia module.
|
||||
final ApplicationPackage? app = _app;
|
||||
final RegExp matchRegExp = app == null
|
||||
? _flutterLogOutput
|
||||
: RegExp('INFO: ${app.name}(\\.cm)?\\(flutter\\): ');
|
||||
return Stream<String>.eventTransformed(
|
||||
lines,
|
||||
(EventSink<String> output) => _FuchsiaLogSink(output, matchRegExp, startTime),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => name;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// The Fuchsia SDK syslog process is killed when the subscription to the
|
||||
// logLines Stream is canceled.
|
||||
}
|
||||
}
|
||||
|
||||
class _FuchsiaLogSink implements EventSink<String> {
|
||||
_FuchsiaLogSink(this._outputSink, this._matchRegExp, this._startTime);
|
||||
|
||||
static final RegExp _utcDateOutput = RegExp(r'\d+\-\d+\-\d+ \d+:\d+:\d+');
|
||||
final EventSink<String> _outputSink;
|
||||
final RegExp _matchRegExp;
|
||||
final DateTime _startTime;
|
||||
|
||||
@override
|
||||
void add(String line) {
|
||||
if (!_matchRegExp.hasMatch(line)) {
|
||||
return;
|
||||
}
|
||||
final String? rawDate = _utcDateOutput.firstMatch(line)?.group(0);
|
||||
if (rawDate == null) {
|
||||
return;
|
||||
}
|
||||
final DateTime logTime = DateTime.parse(rawDate);
|
||||
if (logTime.millisecondsSinceEpoch < _startTime.millisecondsSinceEpoch) {
|
||||
return;
|
||||
}
|
||||
_outputSink.add(
|
||||
'[${logTime.toLocal()}] Flutter: ${line.split(_matchRegExp).last}');
|
||||
}
|
||||
|
||||
@override
|
||||
void addError(Object error, [StackTrace? stackTrace]) {
|
||||
_outputSink.addError(error, stackTrace);
|
||||
}
|
||||
|
||||
@override
|
||||
void close() {
|
||||
_outputSink.close();
|
||||
}
|
||||
}
|
||||
|
||||
/// Device discovery for Fuchsia devices.
|
||||
class FuchsiaDevices extends PollingDeviceDiscovery {
|
||||
FuchsiaDevices({
|
||||
required Platform platform,
|
||||
required FuchsiaWorkflow fuchsiaWorkflow,
|
||||
required FuchsiaSdk fuchsiaSdk,
|
||||
required Logger logger,
|
||||
}) : _platform = platform,
|
||||
_fuchsiaWorkflow = fuchsiaWorkflow,
|
||||
_fuchsiaSdk = fuchsiaSdk,
|
||||
_logger = logger,
|
||||
super('Fuchsia devices');
|
||||
|
||||
final Platform _platform;
|
||||
final FuchsiaWorkflow _fuchsiaWorkflow;
|
||||
final FuchsiaSdk _fuchsiaSdk;
|
||||
final Logger _logger;
|
||||
|
||||
@override
|
||||
bool get supportsPlatform => isFuchsiaSupportedPlatform(_platform);
|
||||
|
||||
@override
|
||||
bool get canListAnything => _fuchsiaWorkflow.canListDevices;
|
||||
|
||||
@override
|
||||
Future<List<Device>> pollingGetDevices({ Duration? timeout }) async {
|
||||
if (!_fuchsiaWorkflow.canListDevices) {
|
||||
return <Device>[];
|
||||
}
|
||||
// TODO(omerlevran): Remove once soft transition is complete fxb/67602.
|
||||
final List<String>? text = (await _fuchsiaSdk.listDevices(
|
||||
timeout: timeout,
|
||||
))?.split('\n');
|
||||
if (text == null || text.isEmpty) {
|
||||
return <Device>[];
|
||||
}
|
||||
return <FuchsiaDevice>[
|
||||
for (final String line in text)
|
||||
if (await _parseDevice(line) case final FuchsiaDevice device) device,
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Future<List<String>> getDiagnostics() async => const <String>[];
|
||||
|
||||
Future<FuchsiaDevice?> _parseDevice(String text) async {
|
||||
final String line = text.trim();
|
||||
// ['ip', 'device name']
|
||||
final List<String> words = line.split(' ');
|
||||
if (words.length < 2) {
|
||||
return null;
|
||||
}
|
||||
final String name = words[1];
|
||||
|
||||
// TODO(omerlevran): Add support for resolve on the FuchsiaSdk Object.
|
||||
final String? resolvedHost = await _fuchsiaSdk.fuchsiaFfx.resolve(name);
|
||||
if (resolvedHost == null) {
|
||||
_logger.printError('Failed to resolve host for Fuchsia device `$name`');
|
||||
return null;
|
||||
}
|
||||
return FuchsiaDevice(resolvedHost, name: name, logger: _logger);
|
||||
}
|
||||
|
||||
@override
|
||||
List<String> get wellKnownIds => const <String>[];
|
||||
}
|
||||
|
||||
class FuchsiaDevice extends Device {
|
||||
FuchsiaDevice(super.id, {required this.name, required super.logger})
|
||||
: super(
|
||||
platformType: PlatformType.fuchsia,
|
||||
category: null,
|
||||
ephemeral: true,
|
||||
);
|
||||
|
||||
@override
|
||||
bool get supportsHotReload => true;
|
||||
|
||||
@override
|
||||
bool get supportsHotRestart => false;
|
||||
|
||||
@override
|
||||
bool get supportsFlutterExit => false;
|
||||
|
||||
@override
|
||||
final String name;
|
||||
|
||||
@override
|
||||
Future<bool> get isLocalEmulator async => false;
|
||||
|
||||
@override
|
||||
Future<String?> get emulatorId async => null;
|
||||
|
||||
@override
|
||||
bool get supportsStartPaused => false;
|
||||
|
||||
late final Future<bool> isSession = _initIsSession();
|
||||
|
||||
/// Determine if the Fuchsia device is running a session based build.
|
||||
///
|
||||
/// If the device is running a session based build, `ffx session` should be
|
||||
/// used to launch apps. Fuchsia flutter apps cannot currently be launched
|
||||
/// without a session.
|
||||
Future<bool> _initIsSession() async {
|
||||
return await globals.fuchsiaSdk?.fuchsiaFfx.sessionShow() != null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> isAppInstalled(
|
||||
ApplicationPackage app, {
|
||||
String? userIdentifier,
|
||||
}) async => false;
|
||||
|
||||
@override
|
||||
Future<bool> isLatestBuildInstalled(ApplicationPackage app) async => false;
|
||||
|
||||
@override
|
||||
Future<bool> installApp(
|
||||
ApplicationPackage app, {
|
||||
String? userIdentifier,
|
||||
}) => Future<bool>.value(false);
|
||||
|
||||
@override
|
||||
Future<bool> uninstallApp(
|
||||
ApplicationPackage app, {
|
||||
String? userIdentifier,
|
||||
}) async => false;
|
||||
|
||||
@override
|
||||
bool isSupported() => true;
|
||||
|
||||
@override
|
||||
bool supportsRuntimeMode(BuildMode buildMode) =>
|
||||
buildMode != BuildMode.jitRelease;
|
||||
|
||||
@override
|
||||
Future<LaunchResult> startApp(
|
||||
FuchsiaApp package, {
|
||||
String? mainPath,
|
||||
String? route,
|
||||
required DebuggingOptions debuggingOptions,
|
||||
Map<String, Object?> platformArgs = const <String, Object?>{},
|
||||
bool prebuiltApplication = false,
|
||||
String? userIdentifier,
|
||||
}) async {
|
||||
if (await isSession) {
|
||||
globals.printTrace('Running on a session framework based build.');
|
||||
} else {
|
||||
globals.printTrace('Running on a non session framework based build.');
|
||||
}
|
||||
|
||||
if (!prebuiltApplication) {
|
||||
throwToolExit(
|
||||
'This tool does not currently build apps for fuchsia.\n'
|
||||
'Build the app using a supported Fuchsia workflow.\n'
|
||||
'Then use the --${FlutterOptions.kUseApplicationBinary} flag.'
|
||||
);
|
||||
}
|
||||
// Stop the app if it's currently running.
|
||||
await stopApp(package);
|
||||
|
||||
// Find out who the device thinks we are.
|
||||
final int port = await globals.os.findFreePort();
|
||||
if (port == 0) {
|
||||
globals.printError('Failed to find a free port');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
// Try Start with a fresh package repo in case one was left over from a
|
||||
// previous run.
|
||||
final Directory packageRepo = globals.fs.directory(
|
||||
globals.fs.path.join(getFuchsiaBuildDirectory(), '.pkg-repo'));
|
||||
try {
|
||||
if (packageRepo.existsSync()) {
|
||||
packageRepo.deleteSync(recursive: true);
|
||||
}
|
||||
packageRepo.createSync(recursive: true);
|
||||
} on Exception catch (e) {
|
||||
globals.printError('Failed to create Fuchsia package repo directory '
|
||||
'at ${packageRepo.path}: $e');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
final String appName = FlutterProject.current().manifest.appName;
|
||||
final Status status = globals.logger.startProgress(
|
||||
'Starting Fuchsia application $appName...',
|
||||
);
|
||||
FuchsiaPackageServer? fuchsiaPackageServer;
|
||||
bool serverRegistered = false;
|
||||
String fuchsiaUrl;
|
||||
try {
|
||||
// Start up a package server.
|
||||
const String packageServerName = FuchsiaPackageServer.toolHost;
|
||||
fuchsiaPackageServer =
|
||||
FuchsiaPackageServer(packageRepo.path, packageServerName, '', port);
|
||||
if (!await fuchsiaPackageServer.start()) {
|
||||
globals.printError('Failed to start the Fuchsia package server');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
// Serve the application's package.
|
||||
final File farArchive =
|
||||
package.farArchive(debuggingOptions.buildInfo.mode);
|
||||
if (!await fuchsiaPackageServer.addPackage(farArchive)) {
|
||||
globals.printError('Failed to add package to the package server');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
// Serve the flutter_runner.
|
||||
final File flutterRunnerArchive =
|
||||
globals.fs.file(globals.artifacts!.getArtifactPath(
|
||||
Artifact.fuchsiaFlutterRunner,
|
||||
platform: await targetPlatform,
|
||||
mode: debuggingOptions.buildInfo.mode,
|
||||
));
|
||||
if (!await fuchsiaPackageServer.addPackage(flutterRunnerArchive)) {
|
||||
globals.printError(
|
||||
'Failed to add flutter_runner package to the package server');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
// Teach the package controller about the package server.
|
||||
if (!await fuchsiaDeviceTools.pkgctl
|
||||
.addRepo(this, fuchsiaPackageServer)) {
|
||||
globals.printError('Failed to teach amber about the package server');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
serverRegistered = true;
|
||||
|
||||
// Tell the package controller to prefetch the flutter_runner.
|
||||
String flutterRunnerName;
|
||||
if (debuggingOptions.buildInfo.usesAot) {
|
||||
if (debuggingOptions.buildInfo.mode.isRelease) {
|
||||
flutterRunnerName = 'flutter_aot_product_runner';
|
||||
} else {
|
||||
flutterRunnerName = 'flutter_aot_runner';
|
||||
}
|
||||
} else {
|
||||
if (debuggingOptions.buildInfo.mode.isRelease) {
|
||||
flutterRunnerName = 'flutter_jit_product_runner';
|
||||
} else {
|
||||
flutterRunnerName = 'flutter_jit_runner';
|
||||
}
|
||||
}
|
||||
|
||||
if (!await fuchsiaDeviceTools.pkgctl
|
||||
.resolve(this, fuchsiaPackageServer.name, flutterRunnerName)) {
|
||||
globals
|
||||
.printError('Failed to get pkgctl to prefetch the flutter_runner');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
// Tell the package controller to prefetch the app.
|
||||
if (!await fuchsiaDeviceTools.pkgctl
|
||||
.resolve(this, fuchsiaPackageServer.name, appName)) {
|
||||
globals.printError('Failed to get pkgctl to prefetch the package');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
|
||||
fuchsiaUrl = 'fuchsia-pkg://$packageServerName/$appName#meta/$appName.cm';
|
||||
|
||||
if (await isSession) {
|
||||
// Instruct ffx session to start the app
|
||||
final bool addedApp =
|
||||
await globals.fuchsiaSdk?.fuchsiaFfx.sessionAdd(fuchsiaUrl) ?? false;
|
||||
if (!addedApp) {
|
||||
globals.printError('Failed to add the app via `ffx session add`');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
} else {
|
||||
globals.printError(
|
||||
'Fuchsia flutter apps can only be launched within a session');
|
||||
return LaunchResult.failed();
|
||||
}
|
||||
} finally {
|
||||
// Try to un-teach the package controller about the package server if
|
||||
// needed.
|
||||
if (serverRegistered && fuchsiaPackageServer != null) {
|
||||
await fuchsiaDeviceTools.pkgctl.rmRepo(this, fuchsiaPackageServer);
|
||||
}
|
||||
// Shutdown the package server and delete the package repo;
|
||||
globals.printTrace("Shutting down the tool's package server.");
|
||||
fuchsiaPackageServer?.stop();
|
||||
globals.printTrace(
|
||||
"Removing the tool's package repo: at ${packageRepo.path}");
|
||||
try {
|
||||
packageRepo.deleteSync(recursive: true);
|
||||
} on Exception catch (e) {
|
||||
globals.printError('Failed to remove Fuchsia package repo directory '
|
||||
'at ${packageRepo.path}: $e.');
|
||||
}
|
||||
status.cancel();
|
||||
}
|
||||
|
||||
if (debuggingOptions.buildInfo.mode.isRelease) {
|
||||
globals.printTrace('App successfully started in a release mode.');
|
||||
return LaunchResult.succeeded();
|
||||
}
|
||||
globals.printTrace(
|
||||
'App started in a non-release mode. Setting up vmservice connection.');
|
||||
|
||||
// In a debug or profile build, try to find the vmService uri.
|
||||
final FuchsiaIsolateDiscoveryProtocol discovery =
|
||||
getIsolateDiscoveryProtocol(appName);
|
||||
try {
|
||||
final Uri vmServiceUri = await discovery.uri;
|
||||
return LaunchResult.succeeded(vmServiceUri: vmServiceUri);
|
||||
} finally {
|
||||
discovery.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> stopApp(
|
||||
ApplicationPackage? app, {
|
||||
String? userIdentifier,
|
||||
}) async {
|
||||
if (await isSession) {
|
||||
// Currently there are no way to close a running app programmatically
|
||||
// using the session framework afaik. So this is a no-op.
|
||||
return true;
|
||||
}
|
||||
// Fuchsia flutter apps currently require a session, but if that changes,
|
||||
// add the relevant "stopApp" code here.
|
||||
return true;
|
||||
}
|
||||
|
||||
Future<TargetPlatform> _queryTargetPlatform() async {
|
||||
const TargetPlatform defaultTargetPlatform = TargetPlatform.fuchsia_arm64;
|
||||
if (!globals.fuchsiaArtifacts!.hasSshConfig) {
|
||||
globals.printTrace('Could not determine Fuchsia target platform because '
|
||||
'Fuchsia ssh configuration is missing.\n'
|
||||
'Defaulting to arm64.');
|
||||
return defaultTargetPlatform;
|
||||
}
|
||||
final RunResult result = await shell('uname -m');
|
||||
if (result.exitCode != 0) {
|
||||
globals.printError(
|
||||
'Could not determine Fuchsia target platform type:\n$result\n'
|
||||
'Defaulting to arm64.');
|
||||
return defaultTargetPlatform;
|
||||
}
|
||||
final String machine = result.stdout.trim();
|
||||
switch (machine) {
|
||||
case 'aarch64':
|
||||
return TargetPlatform.fuchsia_arm64;
|
||||
case 'x86_64':
|
||||
return TargetPlatform.fuchsia_x64;
|
||||
default:
|
||||
globals.printError('Unknown Fuchsia target platform "$machine". '
|
||||
'Defaulting to arm64.');
|
||||
return defaultTargetPlatform;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
bool get supportsScreenshot => isFuchsiaSupportedPlatform(globals.platform);
|
||||
|
||||
@override
|
||||
Future<void> takeScreenshot(File outputFile) async {
|
||||
if (outputFile.basename.split('.').last != 'ppm') {
|
||||
throw Exception('${outputFile.path} must be a .ppm file');
|
||||
}
|
||||
final RunResult screencapResult =
|
||||
await shell('screencap > /tmp/screenshot.ppm');
|
||||
if (screencapResult.exitCode != 0) {
|
||||
throw Exception(
|
||||
'Could not take a screenshot on device $name:\n$screencapResult');
|
||||
}
|
||||
try {
|
||||
final RunResult scpResult =
|
||||
await scp('/tmp/screenshot.ppm', outputFile.path);
|
||||
if (scpResult.exitCode != 0) {
|
||||
throw Exception('Failed to copy screenshot from device:\n$scpResult');
|
||||
}
|
||||
} finally {
|
||||
try {
|
||||
final RunResult deleteResult = await shell('rm /tmp/screenshot.ppm');
|
||||
if (deleteResult.exitCode != 0) {
|
||||
globals.printError(
|
||||
'Failed to delete screenshot.ppm from the device:\n$deleteResult');
|
||||
}
|
||||
} on Exception catch (e) {
|
||||
globals
|
||||
.printError('Failed to delete screenshot.ppm from the device: $e');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
late final Future<TargetPlatform> targetPlatform = _queryTargetPlatform();
|
||||
|
||||
@override
|
||||
Future<String> get sdkNameAndVersion async {
|
||||
const String defaultName = 'Fuchsia';
|
||||
if (!globals.fuchsiaArtifacts!.hasSshConfig) {
|
||||
globals.printTrace('Could not determine Fuchsia sdk name or version '
|
||||
'because Fuchsia ssh configuration is missing.');
|
||||
return defaultName;
|
||||
}
|
||||
const String versionPath = '/pkgfs/packages/build-info/0/data/version';
|
||||
final RunResult catResult = await shell('cat $versionPath');
|
||||
if (catResult.exitCode != 0) {
|
||||
globals.printTrace('Failed to cat $versionPath: ${catResult.stderr}');
|
||||
return defaultName;
|
||||
}
|
||||
final String version = catResult.stdout.trim();
|
||||
if (version.isEmpty) {
|
||||
globals.printTrace('$versionPath was empty');
|
||||
return defaultName;
|
||||
}
|
||||
return 'Fuchsia $version';
|
||||
}
|
||||
|
||||
@override
|
||||
DeviceLogReader getLogReader({
|
||||
ApplicationPackage? app,
|
||||
bool includePastLogs = false,
|
||||
}) {
|
||||
assert(!includePastLogs, 'Past log reading not supported on Fuchsia.');
|
||||
return _logReader ??= _FuchsiaLogReader(this, globals.systemClock, app);
|
||||
}
|
||||
|
||||
_FuchsiaLogReader? _logReader;
|
||||
|
||||
@override
|
||||
DevicePortForwarder get portForwarder =>
|
||||
_portForwarder ??= _FuchsiaPortForwarder(this);
|
||||
DevicePortForwarder? _portForwarder;
|
||||
|
||||
@visibleForTesting
|
||||
set portForwarder(DevicePortForwarder forwarder) {
|
||||
_portForwarder = forwarder;
|
||||
}
|
||||
|
||||
@override
|
||||
void clearLogs() {}
|
||||
|
||||
@override
|
||||
VMServiceDiscoveryForAttach getVMServiceDiscoveryForAttach({
|
||||
String? appId,
|
||||
String? fuchsiaModule,
|
||||
int? filterDevicePort,
|
||||
int? expectedHostPort,
|
||||
required bool ipv6,
|
||||
required Logger logger,
|
||||
}) {
|
||||
if (fuchsiaModule == null) {
|
||||
throwToolExit("'--module' is required for attaching to a Fuchsia device");
|
||||
}
|
||||
if (expectedHostPort != null) {
|
||||
throwToolExit("'--host-vmservice-port' is not supported when attaching to a Fuchsia device");
|
||||
}
|
||||
return FuchsiaIsolateVMServiceDiscoveryForAttach(getIsolateDiscoveryProtocol(fuchsiaModule));
|
||||
}
|
||||
|
||||
/// [true] if the current host address is IPv6.
|
||||
late final bool ipv6 = isIPv6Address(id);
|
||||
|
||||
/// Return the address that the device should use to communicate with the
|
||||
/// host.
|
||||
late final Future<String> hostAddress = () async {
|
||||
final RunResult result = await shell(r'echo $SSH_CONNECTION');
|
||||
void fail() {
|
||||
throwToolExit('Failed to get local address, aborting.\n$result');
|
||||
}
|
||||
|
||||
if (result.exitCode != 0) {
|
||||
fail();
|
||||
}
|
||||
final List<String> splitResult = result.stdout.split(' ');
|
||||
if (splitResult.isEmpty) {
|
||||
fail();
|
||||
}
|
||||
final String addr = splitResult[0].replaceAll('%', '%25');
|
||||
if (addr.isEmpty) {
|
||||
fail();
|
||||
}
|
||||
return addr;
|
||||
}();
|
||||
|
||||
/// List the ports currently running a dart vmService.
|
||||
Future<List<int>> servicePorts() async {
|
||||
const String findCommand = 'find /hub -name vmservice-port';
|
||||
final RunResult findResult = await shell(findCommand);
|
||||
if (findResult.exitCode != 0) {
|
||||
throwToolExit(
|
||||
"'$findCommand' on device $name failed. stderr: '${findResult.stderr}'");
|
||||
}
|
||||
final String findOutput = findResult.stdout;
|
||||
if (findOutput.trim() == '') {
|
||||
throwToolExit(
|
||||
'No Dart Observatories found. Are you running a debug build?');
|
||||
}
|
||||
final List<int> ports = <int>[];
|
||||
for (final String path in findOutput.split('\n')) {
|
||||
if (path == '') {
|
||||
continue;
|
||||
}
|
||||
final String lsCommand = 'ls $path';
|
||||
final RunResult lsResult = await shell(lsCommand);
|
||||
if (lsResult.exitCode != 0) {
|
||||
throwToolExit("'$lsCommand' on device $name failed");
|
||||
}
|
||||
final String lsOutput = lsResult.stdout;
|
||||
for (final String line in lsOutput.split('\n')) {
|
||||
if (line == '') {
|
||||
continue;
|
||||
}
|
||||
final int? port = int.tryParse(line);
|
||||
if (port != null) {
|
||||
ports.add(port);
|
||||
}
|
||||
}
|
||||
}
|
||||
return ports;
|
||||
}
|
||||
|
||||
/// Run `command` on the Fuchsia device shell.
|
||||
Future<RunResult> shell(String command) async {
|
||||
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
|
||||
if (sshConfig == null) {
|
||||
throwToolExit('Cannot interact with device. No ssh config.\n'
|
||||
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
|
||||
}
|
||||
return globals.processUtils.run(<String>[
|
||||
'ssh',
|
||||
'-F',
|
||||
sshConfig.absolute.path,
|
||||
id, // Device's IP address.
|
||||
command,
|
||||
]);
|
||||
}
|
||||
|
||||
/// Transfer the file [origin] from the device to [destination].
|
||||
Future<RunResult> scp(String origin, String destination) async {
|
||||
final File? sshConfig = globals.fuchsiaArtifacts!.sshConfig;
|
||||
if (sshConfig == null) {
|
||||
throwToolExit('Cannot interact with device. No ssh config.\n'
|
||||
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
|
||||
}
|
||||
return globals.processUtils.run(<String>[
|
||||
'scp',
|
||||
'-F',
|
||||
sshConfig.absolute.path,
|
||||
'$id:$origin',
|
||||
destination,
|
||||
]);
|
||||
}
|
||||
|
||||
/// Finds the first port running a VM matching `isolateName` from the
|
||||
/// provided set of `ports`.
|
||||
///
|
||||
/// Returns null if no isolate port can be found.
|
||||
Future<int> findIsolatePort(String isolateName, List<int> ports) async {
|
||||
for (final int port in ports) {
|
||||
try {
|
||||
// The square-bracket enclosure for using the IPv6 loopback
|
||||
// didn't appear to work, but when assigning to the IPv4 loopback device,
|
||||
// netstat shows that the local port is actually being used on the IPv6
|
||||
// loopback (::1).
|
||||
final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$port');
|
||||
final FlutterVmService vmService =
|
||||
await connectToVmService(uri, logger: globals.logger);
|
||||
final List<FlutterView> flutterViews =
|
||||
await vmService.getFlutterViews();
|
||||
for (final FlutterView flutterView in flutterViews) {
|
||||
final vm_service.IsolateRef? uiIsolate = flutterView.uiIsolate;
|
||||
if (uiIsolate == null) {
|
||||
continue;
|
||||
}
|
||||
final int? port = vmService.httpAddress?.port;
|
||||
if (port != null &&
|
||||
(uiIsolate.name?.contains(isolateName) ?? false)) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
} on SocketException catch (err) {
|
||||
globals.printTrace('Failed to connect to $port: $err');
|
||||
}
|
||||
}
|
||||
throwToolExit('No ports found running $isolateName');
|
||||
}
|
||||
|
||||
FuchsiaIsolateDiscoveryProtocol getIsolateDiscoveryProtocol(
|
||||
String isolateName) {
|
||||
return FuchsiaIsolateDiscoveryProtocol(this, isolateName);
|
||||
}
|
||||
|
||||
@override
|
||||
bool isSupportedForProject(FlutterProject flutterProject) {
|
||||
return flutterProject.fuchsia.existsSync();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
await _portForwarder?.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
class FuchsiaIsolateVMServiceDiscoveryForAttach extends VMServiceDiscoveryForAttach {
|
||||
FuchsiaIsolateVMServiceDiscoveryForAttach(this.isolateDiscoveryProtocol);
|
||||
final FuchsiaIsolateDiscoveryProtocol isolateDiscoveryProtocol;
|
||||
|
||||
@override
|
||||
Stream<Uri> get uris {
|
||||
final Future<Uri> uriFuture = (() async {
|
||||
// Wrapping the call in an anonymous async function for easier error handling.
|
||||
try {
|
||||
return await isolateDiscoveryProtocol.uri;
|
||||
} on Exception {
|
||||
final FuchsiaDevice device = isolateDiscoveryProtocol._device;
|
||||
isolateDiscoveryProtocol.dispose();
|
||||
final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
|
||||
for (final ForwardedPort port in ports) {
|
||||
await device.portForwarder.unforward(port);
|
||||
}
|
||||
rethrow;
|
||||
}
|
||||
})();
|
||||
return Stream<Uri>.fromFuture(uriFuture).asBroadcastStream();
|
||||
}
|
||||
}
|
||||
|
||||
class FuchsiaIsolateDiscoveryProtocol {
|
||||
FuchsiaIsolateDiscoveryProtocol(
|
||||
this._device,
|
||||
this._isolateName, [
|
||||
this._vmServiceConnector = _kDefaultFuchsiaIsolateDiscoveryConnector,
|
||||
this._ddsStarter = _kDefaultDartDevelopmentServiceStarter,
|
||||
this._pollOnce = false,
|
||||
]);
|
||||
|
||||
static const Duration _pollDuration = Duration(seconds: 10);
|
||||
final Map<int, FlutterVmService> _ports = <int, FlutterVmService>{};
|
||||
final FuchsiaDevice _device;
|
||||
final String _isolateName;
|
||||
final Completer<Uri> _foundUri = Completer<Uri>();
|
||||
final Future<FlutterVmService> Function(Uri) _vmServiceConnector;
|
||||
final Future<void> Function(Device, Uri, bool) _ddsStarter;
|
||||
// whether to only poll once.
|
||||
final bool _pollOnce;
|
||||
Timer? _pollingTimer;
|
||||
Status? _status;
|
||||
|
||||
FutureOr<Uri> get uri {
|
||||
if (_uri != null) {
|
||||
return _uri!;
|
||||
}
|
||||
_status ??= globals.logger.startProgress(
|
||||
'Waiting for a connection from $_isolateName on ${_device.name}...',
|
||||
);
|
||||
unawaited(_findIsolate()); // Completes the _foundUri Future.
|
||||
return _foundUri.future.then((Uri uri) {
|
||||
_uri = uri;
|
||||
return uri;
|
||||
});
|
||||
}
|
||||
|
||||
Uri? _uri;
|
||||
|
||||
void dispose() {
|
||||
if (!_foundUri.isCompleted) {
|
||||
_status?.cancel();
|
||||
_status = null;
|
||||
_pollingTimer?.cancel();
|
||||
_pollingTimer = null;
|
||||
_foundUri.completeError(Exception('Did not complete'));
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _findIsolate() async {
|
||||
final List<int> ports = await _device.servicePorts();
|
||||
for (final int port in ports) {
|
||||
FlutterVmService? service;
|
||||
if (_ports.containsKey(port)) {
|
||||
service = _ports[port];
|
||||
} else {
|
||||
final int localPort = await _device.portForwarder.forward(port);
|
||||
try {
|
||||
final Uri uri = Uri.parse('http://[$_ipv6Loopback]:$localPort');
|
||||
await _ddsStarter(_device, uri, true);
|
||||
service = await _vmServiceConnector(_device.dds.uri!);
|
||||
_ports[port] = service;
|
||||
} on SocketException catch (err) {
|
||||
globals.printTrace('Failed to connect to $localPort: $err');
|
||||
continue;
|
||||
}
|
||||
}
|
||||
final List<FlutterView> flutterViews =
|
||||
await service?.getFlutterViews() ?? <FlutterView>[];
|
||||
for (final FlutterView flutterView in flutterViews) {
|
||||
final vm_service.IsolateRef? uiIsolate = flutterView.uiIsolate;
|
||||
if (uiIsolate == null) {
|
||||
continue;
|
||||
}
|
||||
final int? port = service?.httpAddress?.port;
|
||||
if (port != null && (uiIsolate.name?.contains(_isolateName) ?? false)) {
|
||||
_foundUri.complete(_device.ipv6
|
||||
? Uri.parse('http://[$_ipv6Loopback]:$port/')
|
||||
: Uri.parse('http://$_ipv4Loopback:$port/'));
|
||||
_status?.stop();
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (_pollOnce) {
|
||||
_foundUri.completeError(Exception('Max iterations exceeded'));
|
||||
_status?.stop();
|
||||
return;
|
||||
}
|
||||
_pollingTimer = Timer(_pollDuration, _findIsolate);
|
||||
}
|
||||
}
|
||||
|
||||
class _FuchsiaPortForwarder extends DevicePortForwarder {
|
||||
_FuchsiaPortForwarder(this.device);
|
||||
|
||||
final FuchsiaDevice device;
|
||||
final Map<int, Process> _processes = <int, Process>{};
|
||||
|
||||
@override
|
||||
Future<int> forward(int devicePort, {int? hostPort}) async {
|
||||
hostPort ??= await globals.os.findFreePort();
|
||||
if (hostPort == 0) {
|
||||
throwToolExit(
|
||||
'Failed to forward port $devicePort. No free host-side ports');
|
||||
}
|
||||
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
|
||||
if (sshConfig == null) {
|
||||
throwToolExit('Cannot interact with device. No ssh config.\n'
|
||||
'Try setting FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR.');
|
||||
}
|
||||
// The provided command works around a bug in -N, see US-515
|
||||
// for more explanation.
|
||||
final List<String> command = <String>[
|
||||
'ssh',
|
||||
'-6',
|
||||
'-F',
|
||||
sshConfig.absolute.path,
|
||||
'-nNT',
|
||||
'-vvv',
|
||||
'-f',
|
||||
'-L',
|
||||
'$hostPort:$_ipv4Loopback:$devicePort',
|
||||
device.id, // Device's IP address.
|
||||
'true',
|
||||
];
|
||||
final Process process = await globals.processManager.start(command);
|
||||
unawaited(process.exitCode.then((int exitCode) {
|
||||
if (exitCode != 0) {
|
||||
throwToolExit('Failed to forward port:$devicePort');
|
||||
}
|
||||
}));
|
||||
_processes[hostPort] = process;
|
||||
_forwardedPorts.add(ForwardedPort(hostPort, devicePort));
|
||||
return hostPort;
|
||||
}
|
||||
|
||||
@override
|
||||
List<ForwardedPort> get forwardedPorts => _forwardedPorts;
|
||||
final List<ForwardedPort> _forwardedPorts = <ForwardedPort>[];
|
||||
|
||||
@override
|
||||
Future<void> unforward(ForwardedPort forwardedPort) async {
|
||||
_forwardedPorts.remove(forwardedPort);
|
||||
final Process? process = _processes.remove(forwardedPort.hostPort);
|
||||
process?.kill();
|
||||
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
|
||||
if (sshConfig == null) {
|
||||
// Nothing to cancel.
|
||||
return;
|
||||
}
|
||||
final List<String> command = <String>[
|
||||
'ssh',
|
||||
'-F',
|
||||
sshConfig.absolute.path,
|
||||
'-O',
|
||||
'cancel',
|
||||
'-vvv',
|
||||
'-L',
|
||||
'${forwardedPort.hostPort}:$_ipv4Loopback:${forwardedPort.devicePort}',
|
||||
device.id, // Device's IP address.
|
||||
];
|
||||
final ProcessResult result = await globals.processManager.run(command);
|
||||
if (result.exitCode != 0) {
|
||||
throwToolExit(
|
||||
'Unforward command failed:\n'
|
||||
'stdout: ${result.stdout}\n'
|
||||
'stderr: ${result.stderr}',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> dispose() async {
|
||||
final List<ForwardedPort> forwardedPortsCopy =
|
||||
List<ForwardedPort>.of(forwardedPorts);
|
||||
for (final ForwardedPort port in forwardedPortsCopy) {
|
||||
await unforward(port);
|
||||
}
|
||||
}
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
// 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 'package:file/file.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../base/common.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/process.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import 'fuchsia_sdk.dart';
|
||||
|
||||
// Usage: ffx [-c <config>] [-e <env>] [-t <target>] [-T <timeout>] [-v] [<command>] [<args>]
|
||||
|
||||
// Fuchsia's developer tool
|
||||
|
||||
// Options:
|
||||
// -c, --config override default configuration
|
||||
// -e, --env override default environment settings
|
||||
// -t, --target apply operations across single or multiple targets
|
||||
// -T, --timeout override default proxy timeout
|
||||
// -v, --verbose use verbose output
|
||||
// --help display usage information
|
||||
|
||||
// Commands:
|
||||
// config View and switch default and user configurations
|
||||
// daemon Interact with/control the ffx daemon
|
||||
// target Interact with a target device or emulator
|
||||
// session Control the current session. See
|
||||
// https://fuchsia.dev/fuchsia-src/concepts/session/introduction
|
||||
// for details.
|
||||
|
||||
/// A simple wrapper for the Fuchsia SDK's 'ffx' tool.
|
||||
class FuchsiaFfx {
|
||||
FuchsiaFfx({
|
||||
FuchsiaArtifacts? fuchsiaArtifacts,
|
||||
Logger? logger,
|
||||
ProcessManager? processManager,
|
||||
}) : _fuchsiaArtifacts = fuchsiaArtifacts ?? globals.fuchsiaArtifacts,
|
||||
_logger = logger ?? globals.logger,
|
||||
_processUtils = ProcessUtils(
|
||||
logger: logger ?? globals.logger,
|
||||
processManager: processManager ?? globals.processManager);
|
||||
|
||||
final FuchsiaArtifacts? _fuchsiaArtifacts;
|
||||
final Logger _logger;
|
||||
final ProcessUtils _processUtils;
|
||||
|
||||
/// Returns a list of attached devices as a list of strings with entries
|
||||
/// formatted as follows:
|
||||
///
|
||||
/// abcd::abcd:abc:abcd:abcd%qemu scare-cable-skip-joy
|
||||
Future<List<String>?> list({Duration? timeout}) async {
|
||||
final File? ffx = _fuchsiaArtifacts?.ffx;
|
||||
if (ffx == null || !ffx.existsSync()) {
|
||||
throwToolExit('Fuchsia ffx tool not found.');
|
||||
}
|
||||
final List<String> command = <String>[
|
||||
ffx.path,
|
||||
if (timeout != null) ...<String>['-T', '${timeout.inSeconds}'],
|
||||
'target',
|
||||
'list',
|
||||
// TODO(akbiggs): Revert -f back to --format once we've verified that
|
||||
// analytics spam is coming from here.
|
||||
'-f',
|
||||
's',
|
||||
];
|
||||
final RunResult result = await _processUtils.run(command);
|
||||
if (result.exitCode != 0) {
|
||||
_logger.printError('ffx failed: ${result.stderr}');
|
||||
return null;
|
||||
}
|
||||
if (result.stderr.contains('No devices found')) {
|
||||
return null;
|
||||
}
|
||||
return result.stdout.split('\n');
|
||||
}
|
||||
|
||||
/// Returns the address of the named device.
|
||||
///
|
||||
/// The string [deviceName] should be the name of the device from the
|
||||
/// 'list' command, e.g. 'scare-cable-skip-joy'.
|
||||
Future<String?> resolve(String deviceName) async {
|
||||
final File? ffx = _fuchsiaArtifacts?.ffx;
|
||||
if (ffx == null || !ffx.existsSync()) {
|
||||
throwToolExit('Fuchsia ffx tool not found.');
|
||||
}
|
||||
final List<String> command = <String>[
|
||||
ffx.path,
|
||||
'target',
|
||||
'list',
|
||||
'-f',
|
||||
'a',
|
||||
deviceName,
|
||||
];
|
||||
final RunResult result = await _processUtils.run(command);
|
||||
if (result.exitCode != 0) {
|
||||
_logger.printError('ffx failed: ${result.stderr}');
|
||||
return null;
|
||||
}
|
||||
return result.stdout.trim();
|
||||
}
|
||||
|
||||
/// Show information about the current session
|
||||
///
|
||||
/// Returns `null` if the command failed, which can be interpreted as there is
|
||||
/// no usable session.
|
||||
Future<String?> sessionShow() async {
|
||||
final File? ffx = _fuchsiaArtifacts?.ffx;
|
||||
if (ffx == null || !ffx.existsSync()) {
|
||||
throwToolExit('Fuchsia ffx tool not found.');
|
||||
}
|
||||
final List<String> command = <String>[
|
||||
ffx.path,
|
||||
'session',
|
||||
'show',
|
||||
];
|
||||
final RunResult result = await _processUtils.run(command);
|
||||
if (result.exitCode != 0) {
|
||||
_logger.printError('ffx failed: ${result.stderr}');
|
||||
return null;
|
||||
}
|
||||
return result.stdout;
|
||||
}
|
||||
|
||||
/// Add an element to the current session
|
||||
///
|
||||
/// [url] should be formatted as a Fuchsia-style package URL, e.g.:
|
||||
/// fuchsia-pkg://fuchsia.com/flutter_gallery#meta/flutter_gallery.cmx
|
||||
/// Returns true on success and false on failure.
|
||||
Future<bool> sessionAdd(String url) async {
|
||||
final File? ffx = _fuchsiaArtifacts?.ffx;
|
||||
if (ffx == null || !ffx.existsSync()) {
|
||||
throwToolExit('Fuchsia ffx tool not found.');
|
||||
}
|
||||
final List<String> command = <String>[
|
||||
ffx.path,
|
||||
'session',
|
||||
'add',
|
||||
url,
|
||||
];
|
||||
final RunResult result = await _processUtils.run(command);
|
||||
if (result.exitCode != 0) {
|
||||
_logger.printError('ffx failed: ${result.stderr}');
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
@ -1,136 +0,0 @@
|
||||
// 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 'package:meta/meta.dart';
|
||||
|
||||
import '../artifacts.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../build_info.dart';
|
||||
import '../globals.dart' as globals;
|
||||
import '../project.dart';
|
||||
|
||||
/// This is a simple wrapper around the custom kernel compiler from the Fuchsia
|
||||
/// SDK.
|
||||
class FuchsiaKernelCompiler {
|
||||
/// Compiles the [fuchsiaProject] with entry point [target] to a collection of
|
||||
/// .dilp files (consisting of the app split along package: boundaries, but
|
||||
/// the Flutter tool should make no use of that fact), and a manifest that
|
||||
/// refers to them.
|
||||
Future<void> build({
|
||||
required FuchsiaProject fuchsiaProject,
|
||||
required String target, // E.g., lib/main.dart
|
||||
BuildInfo buildInfo = BuildInfo.dummy,
|
||||
}) async {
|
||||
// TODO(zanderso): Use filesystem root and scheme information from buildInfo.
|
||||
const String multiRootScheme = 'main-root';
|
||||
final String outDir = getFuchsiaBuildDirectory();
|
||||
final String appName = fuchsiaProject.project.manifest.appName;
|
||||
final String fsRoot = fuchsiaProject.project.directory.path;
|
||||
final String relativePackageConfigPath = globals.fs.path.relative(
|
||||
buildInfo.packageConfigPath,
|
||||
from: fsRoot,
|
||||
);
|
||||
final String manifestPath = globals.fs.path.join(outDir, '$appName.dilpmanifest');
|
||||
final String? kernelCompiler = globals.artifacts?.getArtifactPath(
|
||||
Artifact.fuchsiaKernelCompiler,
|
||||
platform: TargetPlatform.fuchsia_arm64, // This file is not arch-specific.
|
||||
mode: buildInfo.mode,
|
||||
);
|
||||
if (kernelCompiler == null || !globals.fs.isFileSync(kernelCompiler)) {
|
||||
throwToolExit('Fuchsia kernel compiler not found at "$kernelCompiler"');
|
||||
}
|
||||
final String? platformDill = globals.artifacts?.getArtifactPath(
|
||||
Artifact.platformKernelDill,
|
||||
platform: TargetPlatform.fuchsia_arm64, // This file is not arch-specific.
|
||||
mode: buildInfo.mode,
|
||||
);
|
||||
if (platformDill == null || !globals.fs.isFileSync(platformDill)) {
|
||||
throwToolExit('Fuchsia platform file not found at "$platformDill"');
|
||||
}
|
||||
List<String> flags = <String>[
|
||||
'--no-sound-null-safety',
|
||||
'--target',
|
||||
'flutter_runner',
|
||||
'--platform',
|
||||
platformDill,
|
||||
'--filesystem-scheme',
|
||||
'main-root',
|
||||
'--filesystem-root',
|
||||
fsRoot,
|
||||
'--packages',
|
||||
'$multiRootScheme:///$relativePackageConfigPath',
|
||||
'--output',
|
||||
globals.fs.path.join(outDir, '$appName.dil'),
|
||||
'--component-name',
|
||||
appName,
|
||||
...getBuildInfoFlags(buildInfo: buildInfo, manifestPath: manifestPath),
|
||||
];
|
||||
|
||||
flags += <String>[
|
||||
'$multiRootScheme:///$target',
|
||||
];
|
||||
|
||||
final String? engineDartBinaryPath = globals.artifacts?.getArtifactPath(Artifact.engineDartBinary);
|
||||
if (engineDartBinaryPath == null) {
|
||||
throwToolExit('Engine dart binary not found at "$engineDartBinaryPath"');
|
||||
}
|
||||
final List<String> command = <String>[
|
||||
engineDartBinaryPath,
|
||||
kernelCompiler,
|
||||
...flags,
|
||||
];
|
||||
final Status status = globals.logger.startProgress(
|
||||
'Building Fuchsia application...',
|
||||
);
|
||||
int result;
|
||||
try {
|
||||
result = await globals.processUtils.stream(command, trace: true);
|
||||
} finally {
|
||||
status.cancel();
|
||||
}
|
||||
if (result != 0) {
|
||||
throwToolExit('Build process failed');
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide flags that are affected by [BuildInfo]
|
||||
@visibleForTesting
|
||||
static List<String> getBuildInfoFlags({
|
||||
required BuildInfo buildInfo,
|
||||
required String manifestPath,
|
||||
}) {
|
||||
return <String>[
|
||||
// AOT/JIT:
|
||||
if (buildInfo.usesAot) ...<String>[
|
||||
'--aot',
|
||||
'--tfa',
|
||||
] else ...<String>[
|
||||
'--no-link-platform',
|
||||
'--split-output-by-packages',
|
||||
'--manifest',
|
||||
manifestPath,
|
||||
],
|
||||
|
||||
// debug, profile, jit release, release:
|
||||
if (buildInfo.isDebug)
|
||||
'--embed-sources'
|
||||
else
|
||||
'--no-embed-sources',
|
||||
|
||||
if (buildInfo.isProfile) ...<String>[
|
||||
'-Ddart.vm.profile=true',
|
||||
'-Ddart.vm.product=false',
|
||||
],
|
||||
|
||||
if (buildInfo.mode.isRelease) ...<String>[
|
||||
'-Ddart.vm.profile=false',
|
||||
'-Ddart.vm.product=true',
|
||||
],
|
||||
|
||||
for (final String dartDefine in buildInfo.dartDefines)
|
||||
'-D$dartDefine',
|
||||
];
|
||||
}
|
||||
}
|
@ -1,241 +0,0 @@
|
||||
// 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 '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/net.dart';
|
||||
import '../base/process.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
|
||||
/// This is a basic wrapper class for the Fuchsia SDK's `pm` tool.
|
||||
class FuchsiaPM {
|
||||
/// Initializes the staging area at [buildPath] for creating the Fuchsia
|
||||
/// package for the app named [appName].
|
||||
///
|
||||
/// When successful, this creates a file under [buildPath] at `meta/package`.
|
||||
///
|
||||
/// NB: The [buildPath] should probably be e.g. `build/fuchsia/pkg`, and the
|
||||
/// [appName] should probably be the name of the app from the pubspec file.
|
||||
Future<bool> init(String buildPath, String appName) {
|
||||
return _runPMCommand(<String>[
|
||||
'-o',
|
||||
buildPath,
|
||||
'-n',
|
||||
appName,
|
||||
'init',
|
||||
]);
|
||||
}
|
||||
|
||||
/// Updates, signs, and seals a Fuchsia package.
|
||||
///
|
||||
/// [buildPath] should be the same [buildPath] passed to [init].
|
||||
/// [manifestPath] must be a file containing lines formatted as follows:
|
||||
///
|
||||
/// data/path/to/file/in/the/package=/path/to/file/on/the/host
|
||||
///
|
||||
/// which describe the contents of the Fuchsia package. It must also contain
|
||||
/// two other entries:
|
||||
///
|
||||
/// meta/$APPNAME.cm=/path/to/cm/on/the/host/$APPNAME.cm
|
||||
/// meta/package=/path/to/package/file/from/init/package
|
||||
///
|
||||
/// where $APPNAME is the same [appName] passed to [init], and meta/package
|
||||
/// is set up to be the file `meta/package` created by [init].
|
||||
Future<bool> build(String buildPath, String manifestPath) {
|
||||
return _runPMCommand(<String>[
|
||||
'-o',
|
||||
buildPath,
|
||||
'-m',
|
||||
manifestPath,
|
||||
'build',
|
||||
]);
|
||||
}
|
||||
|
||||
/// Constructs a .far representation of the Fuchsia package.
|
||||
///
|
||||
/// When successful, creates a file `app_name-0.far` under [buildPath], which
|
||||
/// is the Fuchsia package.
|
||||
///
|
||||
/// [buildPath] should be the same path passed to [init], and [manifestPath]
|
||||
/// should be the same manifest passed to [build].
|
||||
Future<bool> archive(String buildPath, String manifestPath) {
|
||||
return _runPMCommand(<String>[
|
||||
'-o',
|
||||
buildPath,
|
||||
'-m',
|
||||
manifestPath,
|
||||
'archive',
|
||||
]);
|
||||
}
|
||||
|
||||
/// Initializes a new package repository at [repoPath] to be later served by
|
||||
/// the 'serve' command.
|
||||
Future<bool> newrepo(String repoPath) {
|
||||
return _runPMCommand(<String>[
|
||||
'newrepo',
|
||||
'-repo',
|
||||
repoPath,
|
||||
]);
|
||||
}
|
||||
|
||||
/// Spawns an http server in a new process for serving Fuchsia packages.
|
||||
///
|
||||
/// The argument [repoPath] should have previously been an argument to
|
||||
/// [newrepo]. The [host] should be the host reported by
|
||||
/// [FuchsiaFfx.resolve], and [port] should be an unused port for the
|
||||
/// http server to bind.
|
||||
Future<Process> serve(String repoPath, String host, int port) async {
|
||||
final File? pm = globals.fuchsiaArtifacts?.pm;
|
||||
if (pm == null) {
|
||||
throwToolExit('Fuchsia pm tool not found');
|
||||
}
|
||||
if (isIPv6Address(host.split('%').first)) {
|
||||
host = '[$host]';
|
||||
}
|
||||
final List<String> command = <String>[
|
||||
pm.path,
|
||||
'serve',
|
||||
'-repo',
|
||||
repoPath,
|
||||
'-l',
|
||||
'$host:$port',
|
||||
'-c',
|
||||
'2',
|
||||
];
|
||||
final Process process = await globals.processUtils.start(command);
|
||||
process.stdout
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen(globals.printTrace);
|
||||
process.stderr
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter())
|
||||
.listen(globals.printError);
|
||||
return process;
|
||||
}
|
||||
|
||||
/// Publishes a Fuchsia package to a served package repository.
|
||||
///
|
||||
/// For a package repo initialized with [newrepo] at [repoPath] and served
|
||||
/// by [serve], this call publishes the `far` package at [packagePath] to
|
||||
/// the repo such that it will be visible to devices connecting to the
|
||||
/// package server.
|
||||
Future<bool> publish(String repoPath, String packagePath) {
|
||||
return _runPMCommand(<String>[
|
||||
'publish',
|
||||
'-a',
|
||||
'-r',
|
||||
repoPath,
|
||||
'-f',
|
||||
packagePath,
|
||||
]);
|
||||
}
|
||||
|
||||
Future<bool> _runPMCommand(List<String> args) async {
|
||||
final File? pm = globals.fuchsiaArtifacts?.pm;
|
||||
if (pm == null) {
|
||||
throwToolExit('Fuchsia pm tool not found');
|
||||
}
|
||||
final List<String> command = <String>[pm.path, ...args];
|
||||
final RunResult result = await globals.processUtils.run(command);
|
||||
return result.exitCode == 0;
|
||||
}
|
||||
}
|
||||
|
||||
/// A class for running and retaining state for a Fuchsia package server.
|
||||
///
|
||||
/// [FuchsiaPackageServer] takes care of initializing the package repository,
|
||||
/// spinning up the package server, publishing packages, and shutting down the
|
||||
/// server.
|
||||
///
|
||||
/// Example usage:
|
||||
/// var server = FuchsiaPackageServer(
|
||||
/// '/path/to/repo',
|
||||
/// 'server_name',
|
||||
/// await FuchsiaFfx.resolve(deviceName),
|
||||
/// await freshPort());
|
||||
/// try {
|
||||
/// await server.start();
|
||||
/// await server.addPackage(farArchivePath);
|
||||
/// ...
|
||||
/// } finally {
|
||||
/// server.stop();
|
||||
/// }
|
||||
class FuchsiaPackageServer {
|
||||
FuchsiaPackageServer(String repo, this.name, String host, int port)
|
||||
: _repo = repo, _host = host, _port = port;
|
||||
|
||||
static const String deviceHost = 'fuchsia.com';
|
||||
static const String toolHost = 'flutter-tool';
|
||||
|
||||
final String _repo;
|
||||
final String _host;
|
||||
final int _port;
|
||||
|
||||
Process? _process;
|
||||
|
||||
// The name used to reference the server by fuchsia-pkg:// urls.
|
||||
final String name;
|
||||
|
||||
int get port => _port;
|
||||
|
||||
/// Uses [FuchsiaPM.newrepo] and [FuchsiaPM.serve] to spin up a new Fuchsia
|
||||
/// package server.
|
||||
///
|
||||
/// Returns false if the repo could not be created or the server could not
|
||||
/// be spawned, and true otherwise.
|
||||
Future<bool> start() async {
|
||||
if (_process != null) {
|
||||
globals.printError('$this already started!');
|
||||
return false;
|
||||
}
|
||||
// initialize a new repo.
|
||||
final FuchsiaPM? fuchsiaPM = globals.fuchsiaSdk?.fuchsiaPM;
|
||||
if (fuchsiaPM == null || !await fuchsiaPM.newrepo(_repo)) {
|
||||
globals.printError('Failed to create a new package server repo');
|
||||
return false;
|
||||
}
|
||||
_process = await fuchsiaPM.serve(_repo, _host, _port);
|
||||
// Put a completer on _process.exitCode to watch for error.
|
||||
unawaited(_process?.exitCode.whenComplete(() {
|
||||
// If _process is null, then the server was stopped deliberately.
|
||||
if (_process != null) {
|
||||
globals.printError('Error running Fuchsia pm tool "serve" command');
|
||||
}
|
||||
}));
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Forcefully stops the package server process by sending it SIGTERM.
|
||||
void stop() {
|
||||
if (_process != null) {
|
||||
_process?.kill();
|
||||
_process = null;
|
||||
}
|
||||
}
|
||||
|
||||
/// Uses [FuchsiaPM.publish] to add the Fuchsia 'far' package at
|
||||
/// [packagePath] to the package server.
|
||||
///
|
||||
/// Returns true on success and false if the server wasn't started or the
|
||||
/// publish command failed.
|
||||
Future<bool> addPackage(File package) async {
|
||||
if (_process == null) {
|
||||
return false;
|
||||
}
|
||||
return (await globals.fuchsiaSdk?.fuchsiaPM.publish(_repo, package.path)) ??
|
||||
false;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
final String p =
|
||||
(_process == null) ? 'stopped' : 'running ${_process?.pid}';
|
||||
return 'FuchsiaPackageServer at $_host:$_port ($p)';
|
||||
}
|
||||
}
|
@ -1,150 +0,0 @@
|
||||
// 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 '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../convert.dart';
|
||||
import '../globals.dart' as globals;
|
||||
|
||||
import 'fuchsia_ffx.dart';
|
||||
import 'fuchsia_kernel_compiler.dart';
|
||||
import 'fuchsia_pm.dart';
|
||||
|
||||
/// Returns [true] if the current platform supports Fuchsia targets.
|
||||
bool isFuchsiaSupportedPlatform(Platform platform) {
|
||||
return false;
|
||||
}
|
||||
|
||||
/// The Fuchsia SDK shell commands.
|
||||
///
|
||||
/// This workflow assumes development within the fuchsia source tree,
|
||||
/// including a working fx command-line tool in the user's PATH.
|
||||
class FuchsiaSdk {
|
||||
/// Interface to the 'pm' tool.
|
||||
late final FuchsiaPM fuchsiaPM = FuchsiaPM();
|
||||
|
||||
/// Interface to the 'kernel_compiler' tool.
|
||||
late final FuchsiaKernelCompiler fuchsiaKernelCompiler = FuchsiaKernelCompiler();
|
||||
|
||||
/// Interface to the 'ffx' tool.
|
||||
late final FuchsiaFfx fuchsiaFfx = FuchsiaFfx();
|
||||
|
||||
/// Returns any attached devices is a newline-denominated String.
|
||||
///
|
||||
/// Example output: abcd::abcd:abc:abcd:abcd%qemu scare-cable-skip-joy
|
||||
Future<String?> listDevices({Duration? timeout}) async {
|
||||
final File? ffx = globals.fuchsiaArtifacts?.ffx;
|
||||
if (ffx == null || !ffx.existsSync()) {
|
||||
return null;
|
||||
}
|
||||
final List<String>? devices = await fuchsiaFfx.list(timeout: timeout);
|
||||
if (devices == null) {
|
||||
return null;
|
||||
}
|
||||
return devices.isNotEmpty ? devices.join('\n') : null;
|
||||
}
|
||||
|
||||
/// Returns the fuchsia system logs for an attached device where
|
||||
/// [id] is the IP address of the device.
|
||||
Stream<String>? syslogs(String id) {
|
||||
Process? process;
|
||||
try {
|
||||
final StreamController<String> controller = StreamController<String>(onCancel: () {
|
||||
process?.kill();
|
||||
});
|
||||
final File? sshConfig = globals.fuchsiaArtifacts?.sshConfig;
|
||||
if (sshConfig == null || !sshConfig.existsSync()) {
|
||||
globals.printError('Cannot read device logs: No ssh config.');
|
||||
globals.printError('Have you set FUCHSIA_SSH_CONFIG or FUCHSIA_BUILD_DIR?');
|
||||
return null;
|
||||
}
|
||||
const String remoteCommand = 'log_listener --clock Local';
|
||||
final List<String> cmd = <String>[
|
||||
'ssh',
|
||||
'-F',
|
||||
sshConfig.absolute.path,
|
||||
id, // The device's IP.
|
||||
remoteCommand,
|
||||
];
|
||||
globals.processManager.start(cmd).then((Process newProcess) {
|
||||
if (controller.isClosed) {
|
||||
return;
|
||||
}
|
||||
process = newProcess;
|
||||
process?.exitCode.whenComplete(controller.close);
|
||||
controller.addStream(process!.stdout
|
||||
.transform(utf8.decoder)
|
||||
.transform(const LineSplitter()));
|
||||
});
|
||||
return controller.stream;
|
||||
} on Exception catch (exception) {
|
||||
globals.printTrace('$exception');
|
||||
}
|
||||
return const Stream<String>.empty();
|
||||
}
|
||||
}
|
||||
|
||||
/// Fuchsia-specific artifacts used to interact with a device.
|
||||
class FuchsiaArtifacts {
|
||||
/// Creates a new [FuchsiaArtifacts].
|
||||
FuchsiaArtifacts({
|
||||
this.sshConfig,
|
||||
this.ffx,
|
||||
this.pm,
|
||||
});
|
||||
|
||||
/// Creates a new [FuchsiaArtifacts] using the cached Fuchsia SDK.
|
||||
///
|
||||
/// Finds tools under bin/cache/artifacts/fuchsia/tools.
|
||||
/// Queries environment variables (first FUCHSIA_BUILD_DIR, then
|
||||
/// FUCHSIA_SSH_CONFIG) to find the ssh configuration needed to talk to
|
||||
/// a device.
|
||||
factory FuchsiaArtifacts.find() {
|
||||
if (!isFuchsiaSupportedPlatform(globals.platform)) {
|
||||
// Don't try to find the artifacts on platforms that are not supported.
|
||||
return FuchsiaArtifacts();
|
||||
}
|
||||
// If FUCHSIA_BUILD_DIR is defined, then look for the ssh_config dir
|
||||
// relative to it. Next, if FUCHSIA_SSH_CONFIG is defined, then use it.
|
||||
// TODO(zanderso): Consider passing the ssh config path in with a flag.
|
||||
File? sshConfig;
|
||||
if (globals.platform.environment.containsKey(_kFuchsiaBuildDir)) {
|
||||
sshConfig = globals.fs.file(globals.fs.path.join(
|
||||
globals.platform.environment[_kFuchsiaBuildDir]!, 'ssh-keys', 'ssh_config'));
|
||||
} else if (globals.platform.environment.containsKey(_kFuchsiaSshConfig)) {
|
||||
sshConfig = globals.fs.file(globals.platform.environment[_kFuchsiaSshConfig]);
|
||||
}
|
||||
|
||||
final String fuchsia = globals.cache.getArtifactDirectory('fuchsia').path;
|
||||
final String tools = globals.fs.path.join(fuchsia, 'tools');
|
||||
final File ffx = globals.fs.file(globals.fs.path.join(tools, 'x64/ffx'));
|
||||
final File pm = globals.fs.file(globals.fs.path.join(tools, 'pm'));
|
||||
|
||||
return FuchsiaArtifacts(
|
||||
sshConfig: sshConfig,
|
||||
ffx: ffx.existsSync() ? ffx : null,
|
||||
pm: pm.existsSync() ? pm : null,
|
||||
);
|
||||
}
|
||||
|
||||
static const String _kFuchsiaSshConfig = 'FUCHSIA_SSH_CONFIG';
|
||||
static const String _kFuchsiaBuildDir = 'FUCHSIA_BUILD_DIR';
|
||||
|
||||
/// The location of the SSH configuration file used to interact with a
|
||||
/// Fuchsia device.
|
||||
final File? sshConfig;
|
||||
|
||||
/// The location of the ffx tool used to locate connected
|
||||
/// Fuchsia devices.
|
||||
final File? ffx;
|
||||
|
||||
/// The pm tool.
|
||||
final File? pm;
|
||||
|
||||
/// Returns true if the [sshConfig] file is not null and exists.
|
||||
bool get hasSshConfig => sshConfig != null && sshConfig!.existsSync();
|
||||
}
|
@ -1,46 +0,0 @@
|
||||
// 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 '../base/context.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../doctor_validator.dart';
|
||||
import '../features.dart';
|
||||
import 'fuchsia_sdk.dart';
|
||||
|
||||
/// The [FuchsiaWorkflow] instance.
|
||||
FuchsiaWorkflow? get fuchsiaWorkflow => context.get<FuchsiaWorkflow>();
|
||||
|
||||
/// The Fuchsia-specific implementation of a [Workflow].
|
||||
///
|
||||
/// This workflow assumes development within the fuchsia source tree,
|
||||
/// including a working fx command-line tool in the user's PATH.
|
||||
class FuchsiaWorkflow implements Workflow {
|
||||
FuchsiaWorkflow({
|
||||
required Platform platform,
|
||||
required FeatureFlags featureFlags,
|
||||
required FuchsiaArtifacts fuchsiaArtifacts,
|
||||
}) : _platform = platform,
|
||||
_featureFlags = featureFlags,
|
||||
_fuchsiaArtifacts = fuchsiaArtifacts;
|
||||
|
||||
final Platform _platform;
|
||||
final FeatureFlags _featureFlags;
|
||||
final FuchsiaArtifacts _fuchsiaArtifacts;
|
||||
|
||||
@override
|
||||
bool get appliesToHostPlatform => _featureFlags.isFuchsiaEnabled && (_platform.isLinux || _platform.isMacOS);
|
||||
|
||||
@override
|
||||
bool get canListDevices {
|
||||
return _fuchsiaArtifacts.ffx != null;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get canLaunchDevices {
|
||||
return _fuchsiaArtifacts.ffx != null && _fuchsiaArtifacts.sshConfig != null;
|
||||
}
|
||||
|
||||
@override
|
||||
bool get canListEmulators => false;
|
||||
}
|
@ -1,44 +0,0 @@
|
||||
// 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 '../base/process.dart';
|
||||
|
||||
import 'fuchsia_device.dart';
|
||||
import 'fuchsia_pm.dart';
|
||||
|
||||
/// Simple wrapper for interacting with the 'pkgctl' tool running on the
|
||||
/// Fuchsia device.
|
||||
class FuchsiaPkgctl {
|
||||
/// Teaches pkgctl on [device] about the Fuchsia package server
|
||||
Future<bool> addRepo(
|
||||
FuchsiaDevice device, FuchsiaPackageServer server) async {
|
||||
final String localIp = await device.hostAddress;
|
||||
final String configUrl = 'http://[$localIp]:${server.port}/config.json';
|
||||
final RunResult result =
|
||||
await device.shell('pkgctl repo add url -n ${server.name} $configUrl');
|
||||
return result.exitCode == 0;
|
||||
}
|
||||
|
||||
/// Instructs pkgctl instance running on [device] to forget about the
|
||||
/// Fuchsia package server with the given name
|
||||
/// pkgctl repo rm fuchsia-pkg://mycorp.com
|
||||
Future<bool> rmRepo(FuchsiaDevice device, FuchsiaPackageServer server) async {
|
||||
final RunResult result = await device.shell(
|
||||
'pkgctl repo rm fuchsia-pkg://${server.name}',
|
||||
);
|
||||
return result.exitCode == 0;
|
||||
}
|
||||
|
||||
/// Instructs the pkgctl instance running on [device] to prefetch the package
|
||||
/// with the given [packageUrl] hosted in the given [serverName].
|
||||
Future<bool> resolve(
|
||||
FuchsiaDevice device,
|
||||
String serverName,
|
||||
String packageName,
|
||||
) async {
|
||||
final String packageUrl = 'fuchsia-pkg://$serverName/$packageName';
|
||||
final RunResult result = await device.shell('pkgctl resolve $packageUrl');
|
||||
return result.exitCode == 0;
|
||||
}
|
||||
}
|
@ -32,7 +32,6 @@ import 'cache.dart';
|
||||
import 'custom_devices/custom_devices_config.dart';
|
||||
import 'device.dart';
|
||||
import 'doctor.dart';
|
||||
import 'fuchsia/fuchsia_sdk.dart';
|
||||
import 'ios/ios_workflow.dart';
|
||||
import 'ios/plist_parser.dart';
|
||||
import 'ios/simulators.dart';
|
||||
@ -71,8 +70,6 @@ Signals get signals => context.get<Signals>() ?? LocalSignals.instance;
|
||||
AndroidStudio? get androidStudio => context.get<AndroidStudio>();
|
||||
AndroidSdk? get androidSdk => context.get<AndroidSdk>();
|
||||
FlutterVersion get flutterVersion => context.get<FlutterVersion>()!;
|
||||
FuchsiaArtifacts? get fuchsiaArtifacts => context.get<FuchsiaArtifacts>();
|
||||
FuchsiaSdk? get fuchsiaSdk => context.get<FuchsiaSdk>();
|
||||
Usage get flutterUsage => context.get<Usage>()!;
|
||||
XcodeProjectInterpreter? get xcodeProjectInterpreter => context.get<XcodeProjectInterpreter>();
|
||||
XCDevice? get xcdevice => context.get<XCDevice>();
|
||||
|
@ -21,7 +21,6 @@ import 'package:flutter_tools/src/commands/daemon.dart';
|
||||
import 'package:flutter_tools/src/daemon.dart';
|
||||
import 'package:flutter_tools/src/device.dart';
|
||||
import 'package:flutter_tools/src/features.dart';
|
||||
import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:flutter_tools/src/ios/ios_workflow.dart';
|
||||
import 'package:flutter_tools/src/preview_device.dart';
|
||||
@ -498,7 +497,6 @@ void main() {
|
||||
}, overrides: <Type, Generator>{
|
||||
AndroidWorkflow: () => FakeAndroidWorkflow(),
|
||||
IOSWorkflow: () => FakeIOSWorkflow(),
|
||||
FuchsiaWorkflow: () => FakeFuchsiaWorkflow(),
|
||||
WindowsWorkflow: () => FakeWindowsWorkflow(),
|
||||
});
|
||||
|
||||
@ -1109,13 +1107,6 @@ class FakeWindowsWorkflow extends Fake implements WindowsWorkflow {
|
||||
final bool canListDevices;
|
||||
}
|
||||
|
||||
class FakeFuchsiaWorkflow extends Fake implements FuchsiaWorkflow {
|
||||
FakeFuchsiaWorkflow({ this.canListDevices = true });
|
||||
|
||||
@override
|
||||
final bool canListDevices;
|
||||
}
|
||||
|
||||
class FakeAndroidWorkflow extends Fake implements AndroidWorkflow {
|
||||
FakeAndroidWorkflow({ this.canListDevices = true });
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user