Support for fuchsia device discovery, workflow, and sdk wrapper (#23282)
diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index c470e17..afa56d4 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart
@@ -26,6 +26,8 @@ import 'device.dart'; import 'doctor.dart'; import 'emulator.dart'; +import 'fuchsia/fuchsia_sdk.dart'; +import 'fuchsia/fuchsia_workflow.dart'; import 'ios/cocoapods.dart'; import 'ios/ios_workflow.dart'; import 'ios/mac.dart'; @@ -62,6 +64,8 @@ Doctor: () => const Doctor(), DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance, EmulatorManager: () => EmulatorManager(), + FuchsiaSdk: () => FuchsiaSdk(), + FuchsiaWorkflow: () => FuchsiaWorkflow(), Flags: () => const EmptyFlags(), FlutterVersion: () => FlutterVersion(const Clock()), GenSnapshot: () => const GenSnapshot(),
diff --git a/packages/flutter_tools/lib/src/device.dart b/packages/flutter_tools/lib/src/device.dart index 7704cea..8225f4f 100644 --- a/packages/flutter_tools/lib/src/device.dart +++ b/packages/flutter_tools/lib/src/device.dart
@@ -11,6 +11,8 @@ import 'base/file_system.dart'; import 'base/utils.dart'; import 'build_info.dart'; +import 'fuchsia/fuchsia_device.dart'; + import 'globals.dart'; import 'ios/devices.dart'; import 'ios/simulators.dart'; @@ -27,6 +29,7 @@ _deviceDiscoverers.add(AndroidDevices()); _deviceDiscoverers.add(IOSDevices()); _deviceDiscoverers.add(IOSSimulators()); + _deviceDiscoverers.add(FuchsiaDevices()); _deviceDiscoverers.add(FlutterTesterDevices()); }
diff --git a/packages/flutter_tools/lib/src/doctor.dart b/packages/flutter_tools/lib/src/doctor.dart index f712fad..409f954 100644 --- a/packages/flutter_tools/lib/src/doctor.dart +++ b/packages/flutter_tools/lib/src/doctor.dart
@@ -19,6 +19,7 @@ import 'base/version.dart'; import 'cache.dart'; import 'device.dart'; +import 'fuchsia/fuchsia_workflow.dart'; import 'globals.dart'; import 'intellij/intellij.dart'; import 'ios/ios_workflow.dart'; @@ -80,6 +81,9 @@ if (androidWorkflow.appliesToHostPlatform) _workflows.add(androidWorkflow); + + if (fuchsiaWorkflow.appliesToHostPlatform) + _workflows.add(fuchsiaWorkflow); } return _workflows; }
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart index 2ce84d6..5fe13ea 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart
@@ -4,10 +4,16 @@ import 'dart:async'; +import 'package:meta/meta.dart'; + import '../application_package.dart'; +import '../base/platform.dart'; import '../build_info.dart'; import '../device.dart'; +import 'fuchsia_sdk.dart'; +import 'fuchsia_workflow.dart'; + /// Read the log for a particular device. class _FuchsiaLogReader extends DeviceLogReader { _FuchsiaLogReader(this._device); @@ -27,6 +33,49 @@ String toString() => name; } +class FuchsiaDevices extends PollingDeviceDiscovery { + FuchsiaDevices() : super('Fuchsia devices'); + + @override + bool get supportsPlatform => platform.isLinux || platform.isMacOS; + + @override + bool get canListAnything => fuchsiaWorkflow.canListDevices; + + @override + Future<List<Device>> pollingGetDevices() async { + if (!fuchsiaWorkflow.canListDevices) { + return <Device>[]; + } + final String text = await fuchsiaSdk.netls(); + return parseFuchsiaDeviceOutput(text); + } + + @override + Future<List<String>> getDiagnostics() async => const <String>[]; +} + +/// Parses output from the netls tool into fuchsia devices. +/// +/// Example output: +/// $ ./netls +/// > device liliac-shore-only-last (fe80::82e4:da4d:fe81:227d/3) +@visibleForTesting +List<FuchsiaDevice> parseFuchsiaDeviceOutput(String text) { + final List<FuchsiaDevice> devices = <FuchsiaDevice>[]; + for (String rawLine in text.trim().split('\n')) { + final String line = rawLine.trim(); + if (!line.startsWith('device')) + continue; + // ['device', 'device name', '(id)'] + final List<String> words = line.split(' '); + final String name = words[1]; + final String id = words[2].substring(1, words[2].length - 1); + devices.add(FuchsiaDevice(id, name: name)); + } + return devices; +} + class FuchsiaDevice extends Device { FuchsiaDevice(String id, { this.name }) : super(id);
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart new file mode 100644 index 0000000..d010d3b --- /dev/null +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart
@@ -0,0 +1,54 @@ +// Copyright 2018 The Chromium 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/common.dart'; +import '../base/context.dart'; +import '../base/process.dart'; + +/// The [FuchsiaSdk] instance. +FuchsiaSdk get fuchsiaSdk => context[FuchsiaSdk]; + +/// 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 { + + /// Invokes the `netaddr` command. + /// + /// This returns the network address of an attached fuchsia device. Does + /// not currently support multiple attached devices. + /// + /// Example output: + /// $ fx netaddr --fuchsia + /// > fe80::9aaa:fcff:fe60:d3af%eth1 + Future<String> netaddr() async { + try { + final RunResult process = await runAsync(<String>['fx', 'netaddr', '--fuchsia', '--nowait']); + return process.stdout; + } on ArgumentError catch (exception) { + throwToolExit('$exception'); + } + return null; + } + + /// Invokes the `netls` command. + /// + /// This lists attached fuchsia devices with their name and address. Does + /// not currently support multiple attached devices. + /// + /// Example output: + /// $ fx netls + /// > device liliac-shore-only-last (fe80::82e4:da4d:fe81:227d/3) + Future<String> netls() async { + try { + final RunResult process = await runAsync( + <String>['fx', 'netls', '--nowait']); + return process.stdout; + } on ArgumentError catch (exception) { + throwToolExit('$exception'); + } + return null; + } +}
diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_workflow.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_workflow.dart new file mode 100644 index 0000000..7978376 --- /dev/null +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_workflow.dart
@@ -0,0 +1,34 @@ +// Copyright 2018 The Chromium 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/os.dart'; +import '../base/platform.dart'; +import '../doctor.dart'; + +/// The [FuchsiaWorkflow] instance. +FuchsiaWorkflow get fuchsiaWorkflow => context[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 { + + @override + bool get appliesToHostPlatform => platform.isLinux || platform.isMacOS; + + @override + bool get canListDevices { + return os.which('fx') != null; + } + + @override + bool get canLaunchDevices { + return os.which('fx') != null; + } + + @override + bool get canListEmulators => false; +}
diff --git a/packages/flutter_tools/test/fuchsia/fuchsa_device_test.dart b/packages/flutter_tools/test/fuchsia/fuchsa_device_test.dart new file mode 100644 index 0000000..8695773 --- /dev/null +++ b/packages/flutter_tools/test/fuchsia/fuchsa_device_test.dart
@@ -0,0 +1,29 @@ +// Copyright 2018 The Chromium 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:flutter_tools/src/fuchsia/fuchsia_device.dart'; + +import '../src/common.dart'; +import '../src/context.dart'; + +void main() { + group('fuchsia device', () { + testUsingContext('stores the requested id and name', () { + const String deviceId = 'e80::0000:a00a:f00f:2002/3'; + const String name = 'halfbaked'; + final FuchsiaDevice device = FuchsiaDevice(deviceId, name: name); + expect(device.id, deviceId); + expect(device.name, name); + }); + + test('parse netls log output', () { + const String example = 'device lilia-shore-only-last (fe80::0000:a00a:f00f:2002/3)'; + final List<FuchsiaDevice> devices = parseFuchsiaDeviceOutput(example); + + expect(devices.length, 1); + expect(devices.first.id, 'fe80::0000:a00a:f00f:2002/3'); + expect(devices.first.name, 'lilia-shore-only-last'); + }); + }); +}
diff --git a/packages/flutter_tools/test/fuchsia/fuchsia_workflow_test.dart b/packages/flutter_tools/test/fuchsia/fuchsia_workflow_test.dart new file mode 100644 index 0000000..974c55f --- /dev/null +++ b/packages/flutter_tools/test/fuchsia/fuchsia_workflow_test.dart
@@ -0,0 +1,41 @@ +// Copyright 2018 The Chromium 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:flutter_tools/src/base/file_system.dart'; +import 'package:flutter_tools/src/base/os.dart'; +import 'package:flutter_tools/src/fuchsia/fuchsia_workflow.dart'; +import 'package:mockito/mockito.dart'; + +import '../src/common.dart'; +import '../src/context.dart'; + +class MockOperatingSystemUtils extends Mock implements OperatingSystemUtils {} +class MockFile extends Mock implements File {} + +void main() { + bool fxPresent = false; + final MockOperatingSystemUtils utils = MockOperatingSystemUtils(); + final MockFile file = MockFile(); + when(utils.which('fx')).thenAnswer((Invocation _) => fxPresent ? file : null); + + group('android workflow', () { + testUsingContext('can not list and launch devices if there is no `fx` command', () { + fxPresent = false; + expect(fuchsiaWorkflow.canLaunchDevices, false); + expect(fuchsiaWorkflow.canListDevices, false); + expect(fuchsiaWorkflow.canListEmulators, false); + }, overrides: <Type, Generator>{ + OperatingSystemUtils: () => utils, + }); + + testUsingContext('can list and launch devices supported if there is a `fx` command', () { + fxPresent = true; + expect(fuchsiaWorkflow.canLaunchDevices, true); + expect(fuchsiaWorkflow.canListDevices, true); + expect(fuchsiaWorkflow.canListEmulators, false); + }, overrides: <Type, Generator>{ + OperatingSystemUtils: () => utils, + }); + }); +}