// 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 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( fuchsiaArtifacts: globals.fuchsiaArtifacts, logger: globals.logger, processManager: globals.processManager, ); /// 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(); }