1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
// 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();
}