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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// 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_null_migrated.dart' as globals;
import 'fuchsia_dev_finder.dart';
import 'fuchsia_ffx.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.
late final FuchsiaPM fuchsiaPM = FuchsiaPM();
/// Interface to the 'device-finder' tool.
late final FuchsiaDevFinder fuchsiaDevFinder = FuchsiaDevFinder(
fuchsiaArtifacts: globals.fuchsiaArtifacts,
logger: globals.logger,
processManager: globals.processManager
);
/// 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, bool useDeviceFinder = false}) async {
List<String>? devices;
if (useDeviceFinder) {
final File? devFinder = globals.fuchsiaArtifacts?.devFinder;
if (devFinder == null || !devFinder.existsSync()) {
return null;
}
devices = await fuchsiaDevFinder.list(timeout: timeout);
} else {
final File? ffx = globals.fuchsiaArtifacts?.ffx;
if (ffx == null || !ffx.existsSync()) {
return null;
}
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.devFinder,
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 devFinder = globals.fs.file(globals.fs.path.join(tools, 'device-finder'));
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,
devFinder: devFinder.existsSync() ? devFinder : null,
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 dev finder tool used to locate connected
/// Fuchsia devices.
final File? devFinder;
/// 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();
}