| // 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/context.dart'; |
| import '../base/file_system.dart'; |
| import '../base/io.dart'; |
| import '../base/platform.dart'; |
| import '../convert.dart'; |
| import '../globals.dart' as globals; |
| |
| import 'fuchsia_dev_finder.dart'; |
| import 'fuchsia_kernel_compiler.dart'; |
| import 'fuchsia_pm.dart'; |
| |
| /// The [FuchsiaSdk] instance. |
| FuchsiaSdk get fuchsiaSdk => context.get<FuchsiaSdk>(); |
| |
| /// Returns [true] if the current platform supports Fuchsia targets. |
| bool isFuchsiaSupportedPlatform(Platform platform) { |
| return platform.isLinux || platform.isMacOS; |
| } |
| |
| /// 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. |
| FuchsiaPM get fuchsiaPM => _fuchsiaPM ??= FuchsiaPM(); |
| FuchsiaPM _fuchsiaPM; |
| |
| /// Interface to the 'device-finder' tool. |
| FuchsiaDevFinder _fuchsiaDevFinder; |
| FuchsiaDevFinder get fuchsiaDevFinder => |
| _fuchsiaDevFinder ??= FuchsiaDevFinder( |
| fuchsiaArtifacts: globals.fuchsiaArtifacts, |
| logger: globals.logger, |
| processManager: globals.processManager |
| ); |
| |
| /// Interface to the 'kernel_compiler' tool. |
| FuchsiaKernelCompiler _fuchsiaKernelCompiler; |
| FuchsiaKernelCompiler get fuchsiaKernelCompiler => |
| _fuchsiaKernelCompiler ??= FuchsiaKernelCompiler(); |
| |
| /// Returns any attached devices is a newline-denominated String. |
| /// |
| /// Example output: |
| /// |
| /// $ device-finder list -full |
| /// > 192.168.42.56 paper-pulp-bush-angel |
| Future<String> listDevices({ Duration timeout }) async { |
| if (globals.fuchsiaArtifacts.devFinder == null || |
| !globals.fuchsiaArtifacts.devFinder.existsSync()) { |
| return null; |
| } |
| final List<String> devices = await fuchsiaDevFinder.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(); |
| }); |
| if (globals.fuchsiaArtifacts.sshConfig == null || |
| !globals.fuchsiaArtifacts.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', |
| globals.fuchsiaArtifacts.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.devFinder, |
| 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(zra): 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 devFinder = globals.fs.file(globals.fs.path.join(tools, 'device-finder')); |
| final File pm = globals.fs.file(globals.fs.path.join(tools, 'pm')); |
| |
| return FuchsiaArtifacts( |
| sshConfig: sshConfig, |
| devFinder: devFinder.existsSync() ? devFinder : 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 dev finder tool used to locate connected |
| /// Fuchsia devices. |
| final File devFinder; |
| |
| /// The pm tool. |
| final File pm; |
| |
| /// Returns true if the [sshConfig] file is not null and exists. |
| bool get hasSshConfig => sshConfig != null && sshConfig.existsSync(); |
| } |