| // 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 '../features.dart' show featureFlags; | |
| 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 featureFlags.isFuchsiaEnabled && (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. | |
| 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(); | |
| } |