Unverified Commit 81c7af34 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add fuchsia specific entrypoint (#23916)

parent cf2fba7b
......@@ -236,3 +236,19 @@ dart_tool("fuchsia_tester") {
"$flutter_root/shell",
]
}
dart_tool("fuchsia_tools") {
package_name = "fuchsia_tools"
main_dart = "bin/fuchsia_tools.dart"
# Can be left empty as analysis is disabled.
sources = []
disable_analysis = true
deps = [
":flutter_tools",
]
# TODO(jonahwilliams): add a frontend_server as a non dart dep.
}
// Copyright 2018 The Chromium 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 'package:flutter_tools/fuchsia_executable.dart' as executable;
void main(List<String> args) {
executable.main(args);
}
......@@ -18,7 +18,6 @@ import 'src/commands/doctor.dart';
import 'src/commands/drive.dart';
import 'src/commands/emulators.dart';
import 'src/commands/format.dart';
import 'src/commands/fuchsia_reload.dart';
import 'src/commands/ide_config.dart';
import 'src/commands/inject_plugins.dart';
import 'src/commands/install.dart';
......@@ -63,7 +62,6 @@ Future<void> main(List<String> args) async {
DriveCommand(),
EmulatorsCommand(),
FormatCommand(),
FuchsiaReloadCommand(),
IdeConfigCommand(hidden: !verboseHelp),
InjectPluginsCommand(hidden: !verboseHelp),
InstallCommand(),
......
// Copyright 2018 The Chromium 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 'package:args/args.dart';
import 'runner.dart' as runner;
import 'src/artifacts.dart';
import 'src/base/common.dart';
import 'src/base/context.dart';
import 'src/base/file_system.dart';
import 'src/commands/attach.dart';
import 'src/commands/devices.dart';
import 'src/commands/shell_completion.dart';
import 'src/fuchsia/fuchsia_sdk.dart';
import 'src/runner/flutter_command.dart';
final ArgParser parser = ArgParser.allowAnything()
..addOption('verbose', abbr: 'v')
..addOption('help', abbr: 'h')
..addOption(
'frontend-server',
help: 'The path to the frontend server snapshot.',
)
..addOption(
'dart-sdk',
help: 'The path to the patched dart-sdk binary.',
)
..addOption(
'ssh-config',
help: 'The path to the ssh configuration file.',
);
/// Main entry point for fuchsia commands.
///
/// This function is intended to be used within the fuchsia source tree.
Future<void> main(List<String> args) async {
final ArgResults results = parser.parse(args);
final bool verbose = results['verbose'];
final bool help = results['help'];
final bool verboseHelp = help && verbose;
final File dartSdk = fs.file(results['dart-sdk']);
final File frontendServer = fs.file(results['frontend-server']);
final File sshConfig = fs.file(results['ssh-config']);
if (!dartSdk.existsSync()) {
throwToolExit('--dart-sdk is required: ${dartSdk.path} does not exist.');
}
if (!frontendServer.existsSync()) {
throwToolExit('--frontend-server is required: ${frontendServer.path} does not exist.');
}
if (!sshConfig.existsSync()) {
throwToolExit('--ssh-config is required: ${sshConfig.path} does not exist.');
}
await runner.run(args, <FlutterCommand>[
AttachCommand(verboseHelp: verboseHelp),
DevicesCommand(),
ShellCompletionCommand(),
], verbose: verbose,
muteCommandLogging: help,
verboseHelp: verboseHelp,
overrides: <Type, Generator>{
FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig),
Artifacts: () => OverrideArtifacts(
parent: CachedArtifacts(),
frontendServer: frontendServer,
engineDartBinary: dartSdk,
)
});
}
......@@ -34,6 +34,7 @@ Future<int> run(
bool verboseHelp = false,
bool reportCrashes,
String flutterVersion,
Map<Type, Generator> overrides,
}) {
reportCrashes ??= !isRunningOnBot;
......@@ -63,7 +64,7 @@ Future<int> run(
return await _handleToolError(error, stackTrace, verbose, args, reportCrashes, getVersion);
}
return 0;
});
}, overrides: overrides);
}
Future<int> _handleToolError(
......
......@@ -303,7 +303,6 @@ class LocalEngineArtifacts extends Artifacts {
/// An implementation of [Artifacts] that provides individual overrides.
///
/// If an artifact is not provided, the lookup delegates to the parent.
/// Currently only allows overriding the location of the [frontendServer].
class OverrideArtifacts implements Artifacts {
/// Creates a new [OverrideArtifacts].
///
......@@ -311,16 +310,21 @@ class OverrideArtifacts implements Artifacts {
OverrideArtifacts({
@required this.parent,
this.frontendServer,
this.engineDartBinary,
}) : assert(parent != null);
final Artifacts parent;
final File frontendServer;
final File engineDartBinary;
@override
String getArtifactPath(Artifact artifact, [TargetPlatform platform, BuildMode mode]) {
if (artifact == Artifact.frontendServerSnapshotForEngineDartSdk && frontendServer != null) {
return frontendServer.path;
}
if (artifact == Artifact.engineDartBinary && engineDartBinary != null) {
return engineDartBinary.path;
}
return parent.getArtifactPath(artifact, platform, mode);
}
......
......@@ -11,6 +11,7 @@ import '../base/logger.dart';
import '../base/utils.dart';
import '../cache.dart';
import '../commands/daemon.dart';
import '../compile.dart';
import '../device.dart';
import '../fuchsia/fuchsia_device.dart';
import '../globals.dart';
......@@ -48,6 +49,7 @@ class AttachCommand extends FlutterCommand {
usesIsolateFilterOption(hide: !verboseHelp);
usesTargetOption();
usesFilesystemOptions(hide: !verboseHelp);
usesFuchsiaOptions(hide: !verboseHelp);
argParser
..addOption(
'debug-port',
......@@ -60,12 +62,6 @@ class AttachCommand extends FlutterCommand {
'project-root',
hide: !verboseHelp,
help: 'Normally used only in run target',
)..addOption(
'module',
abbr: 'm',
hide: !verboseHelp,
help: 'The name of the module (required if attaching to a fuchsia device)',
valueHelp: 'module-name',
)..addFlag('machine',
hide: !verboseHelp,
negatable: false,
......@@ -180,6 +176,7 @@ class AttachCommand extends FlutterCommand {
fileSystemRoots: argResults['filesystem-root'],
fileSystemScheme: argResults['filesystem-scheme'],
viewFilter: argResults['isolate-filter'],
targetModel: TargetModel(argResults['target-model']),
);
flutterDevice.observatoryUris = <Uri>[ observatoryUri ];
final HotRunner hotRunner = hotRunnerFactory.build(
......
// Copyright 2017 The Chromium 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 'dart:collection';
import 'dart:convert';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/process_manager.dart';
import '../base/terminal.dart';
import '../base/utils.dart';
import '../bundle.dart' as bundle;
import '../cache.dart';
import '../context_runner.dart';
import '../device.dart';
import '../fuchsia/fuchsia_device.dart';
import '../globals.dart';
import '../resident_runner.dart';
import '../run_hot.dart';
import '../runner/flutter_command.dart';
import '../vmservice.dart';
// Usage:
// With e.g. hello_mod already running, a HotRunner can be attached to it.
//
// From a Fuchsia in-tree build:
// $ flutter fuchsia_reload --address 192.168.1.39 \
// --build-dir ~/fuchsia/out/x64 \
// --gn-target //topaz/examples/ui/hello_mod:hello_mod
//
// From out of tree:
// $ flutter fuchsia_reload --address 192.168.1.39 \
// --mod_name hello_mod \
// --path /path/to/hello_mod
// --dot-packages /path/to/hello_mod_out/app.packages \
// --ssh-config /path/to/ssh_config \
// --target /path/to/hello_mod/lib/main.dart
final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
class FuchsiaReloadCommand extends FlutterCommand {
FuchsiaReloadCommand() {
addBuildModeFlags(defaultToRelease: false);
argParser.addOption('frontend-server',
abbr: 'f',
help: 'The frontend server location');
argParser.addOption('address',
abbr: 'a',
help: 'Fuchsia device network name or address.');
argParser.addOption('build-dir',
abbr: 'b',
defaultsTo: null,
help: 'Fuchsia build directory, e.g. out/release-x86-64.');
argParser.addOption('dot-packages',
abbr: 'd',
defaultsTo: null,
help: 'Path to the mod\'s .packages file. Required if no'
'GN target specified.');
argParser.addOption('gn-target',
abbr: 'g',
help: 'GN target of the application, e.g //path/to/app:app.');
argParser.addOption('isolate-number',
abbr: 'i',
help: 'To reload only one instance, specify the isolate number, e.g. '
'the number in foo\$main-###### given by --list.');
argParser.addFlag('list',
abbr: 'l',
defaultsTo: false,
help: 'Lists the running modules. ');
argParser.addOption('mod-name',
abbr: 'm',
help: 'Name of the flutter mod. If used with -g, overrides the name '
'inferred from the GN target.');
argParser.addOption('path',
abbr: 'p',
defaultsTo: null,
help: 'Path to the flutter mod project.');
argParser.addOption('ssh-config',
abbr: 's',
defaultsTo: null,
help: 'Path to the Fuchsia target\'s ssh config file.');
argParser.addOption('target',
abbr: 't',
defaultsTo: bundle.defaultMainPath,
help: 'Target app path / main entry-point file. '
'Relative to --path or --gn-target path, e.g. lib/main.dart.');
}
@override
final String name = 'fuchsia_reload';
@override
final String description = 'Hot reload on Fuchsia.';
String _modName;
String _isolateNumber;
String _fuchsiaProjectPath;
String _target;
String _address;
String _dotPackagesPath;
String _sshConfig;
File _frontendServerSnapshot;
bool _list;
@override
Future<FlutterCommandResult> runCommand() async {
Cache.releaseLockEarly();
await _validateArguments();
await runInContext<void>(() async {
// Find the network ports used on the device by VM service instances.
final List<int> deviceServicePorts = await _getServicePorts();
if (deviceServicePorts.isEmpty)
throwToolExit('Couldn\'t find any running Observatory instances.');
for (int port in deviceServicePorts)
printTrace('Fuchsia service port: $port');
// Set up ssh tunnels to forward the device ports to local ports.
final List<_PortForwarder> forwardedPorts = await _forwardPorts(deviceServicePorts);
// Wrap everything in try/finally to make sure we kill the ssh processes
// doing the port forwarding.
try {
final List<int> servicePorts = forwardedPorts.map<int>((_PortForwarder pf) => pf.port).toList();
if (_list) {
await _listVMs(servicePorts);
// Port forwarding stops when the command ends. Keep the program running
// until directed by the user so that Observatory URLs that we print
// continue to work.
printStatus('Press Enter to exit.');
await stdin.first;
return null;
}
// Check that there are running VM services on the returned
// ports, and find the Isolates that are running the target app.
final String isolateName = '$_modName\$main$_isolateNumber';
final List<int> targetPorts = await _filterPorts(servicePorts, isolateName);
if (targetPorts.isEmpty)
throwToolExit('No VMs found running $_modName.');
for (int port in targetPorts)
printTrace('Found $_modName at $port');
// Set up a device and hot runner and attach the hot runner to the first
// vm service we found.
final List<String> fullAddresses =
targetPorts.map<String>((int p) => '$ipv4Loopback:$p').toList();
final List<Uri> observatoryUris = fullAddresses
.map<Uri>((String a) => Uri.parse('http://$a'))
.toList();
final FuchsiaDevice device =
FuchsiaDevice(fullAddresses[0], name: _address);
final FlutterDevice flutterDevice = FlutterDevice(
device,
trackWidgetCreation: false,
viewFilter: isolateName,
);
flutterDevice.observatoryUris = observatoryUris;
final HotRunner hotRunner = HotRunner(<FlutterDevice>[flutterDevice],
debuggingOptions: DebuggingOptions.enabled(getBuildInfo()),
target: _target,
projectRootPath: _fuchsiaProjectPath,
packagesFilePath: _dotPackagesPath,
);
printStatus('Connecting to $_modName');
await hotRunner.attach();
} finally {
await Future.wait<void>(forwardedPorts.map<Future<void>>((_PortForwarder pf) => pf.stop()));
}
}, overrides: <Type, Generator>{
Artifacts: () => OverrideArtifacts(parent: artifacts, frontendServer: _frontendServerSnapshot),
});
return null;
}
// A cache of VMService connections.
final HashMap<int, VMService> _vmServiceCache = HashMap<int, VMService>();
Future<VMService> _getVMService(int port) async {
if (!_vmServiceCache.containsKey(port)) {
final String addr = 'http://$ipv4Loopback:$port';
final Uri uri = Uri.parse(addr);
final VMService vmService = await VMService.connect(uri);
_vmServiceCache[port] = vmService;
}
return _vmServiceCache[port];
}
Future<List<FlutterView>> _getViews(List<int> ports) async {
final List<FlutterView> views = <FlutterView>[];
for (int port in ports) {
final VMService vmService = await _getVMService(port);
await vmService.getVM();
await vmService.refreshViews();
views.addAll(vmService.vm.views);
}
return views;
}
// Find ports where there is a view isolate with the given name
Future<List<int>> _filterPorts(List<int> ports, String viewFilter) async {
printTrace('Looing for view $viewFilter');
final List<int> result = <int>[];
for (FlutterView v in await _getViews(ports)) {
if (v.uiIsolate == null)
continue;
final Uri addr = v.owner.vmService.httpAddress;
printTrace('At $addr, found view: ${v.uiIsolate.name}');
if (v.uiIsolate.name.contains(viewFilter))
result.add(addr.port);
}
return result;
}
String _vmServiceToString(VMService vmService, {int tabDepth = 0}) {
final Uri addr = vmService.httpAddress;
final String embedder = vmService.vm.embedder;
final int numIsolates = vmService.vm.isolates.length;
final String maxRSS = getSizeAsMB(vmService.vm.maxRSS);
final String heapSize = getSizeAsMB(vmService.vm.heapAllocatedMemoryUsage);
int totalNewUsed = 0;
int totalNewCap = 0;
int totalOldUsed = 0;
int totalOldCap = 0;
int totalExternal = 0;
for (Isolate i in vmService.vm.isolates) {
totalNewUsed += i.newSpace.used;
totalNewCap += i.newSpace.capacity;
totalOldUsed += i.oldSpace.used;
totalOldCap += i.oldSpace.capacity;
totalExternal += i.newSpace.external;
totalExternal += i.oldSpace.external;
}
final String newUsed = getSizeAsMB(totalNewUsed);
final String newCap = getSizeAsMB(totalNewCap);
final String oldUsed = getSizeAsMB(totalOldUsed);
final String oldCap = getSizeAsMB(totalOldCap);
final String external = getSizeAsMB(totalExternal);
final String tabs = '\t' * tabDepth;
final String extraTabs = '\t' * (tabDepth + 1);
final StringBuffer stringBuffer = StringBuffer(
'$tabs${terminal.bolden('$embedder at $addr')}\n'
'${extraTabs}RSS: $maxRSS\n'
'${extraTabs}Native allocations: $heapSize\n'
'${extraTabs}New Spaces: $newUsed of $newCap\n'
'${extraTabs}Old Spaces: $oldUsed of $oldCap\n'
'${extraTabs}External: $external\n'
'${extraTabs}Isolates: $numIsolates\n'
);
for (Isolate isolate in vmService.vm.isolates) {
stringBuffer.write(_isolateToString(isolate, tabDepth: tabDepth + 1));
}
return stringBuffer.toString();
}
String _isolateToString(Isolate isolate, {int tabDepth = 0}) {
final Uri vmServiceAddr = isolate.owner.vmService.httpAddress;
final String name = isolate.name;
final String shortName = name.substring(0, name.indexOf('\$'));
const String main = '\$main-';
final String number = name.substring(name.indexOf(main) + main.length);
// The Observatory requires somewhat non-standard URIs that the Uri class
// can't build for us, so instead we build them by hand.
final String isolateIdQuery = '?isolateId=isolates%2F$number';
final String isolateAddr = '$vmServiceAddr/#/inspect$isolateIdQuery';
final String debuggerAddr = '$vmServiceAddr/#/debugger$isolateIdQuery';
final String newUsed = getSizeAsMB(isolate.newSpace.used);
final String newCap = getSizeAsMB(isolate.newSpace.capacity);
final String newFreq = '${isolate.newSpace.avgCollectionTime.inMilliseconds}ms';
final String newPer = '${isolate.newSpace.avgCollectionPeriod.inSeconds}s';
final String oldUsed = getSizeAsMB(isolate.oldSpace.used);
final String oldCap = getSizeAsMB(isolate.oldSpace.capacity);
final String oldFreq = '${isolate.oldSpace.avgCollectionTime.inMilliseconds}ms';
final String oldPer = '${isolate.oldSpace.avgCollectionPeriod.inSeconds}s';
final String external = getSizeAsMB(isolate.newSpace.external + isolate.oldSpace.external);
final String tabs = '\t' * tabDepth;
final String extraTabs = '\t' * (tabDepth + 1);
return
'$tabs${terminal.bolden(shortName)}\n'
'${extraTabs}Isolate number: $number\n'
'${extraTabs}Observatory: $isolateAddr\n'
'${extraTabs}Debugger: $debuggerAddr\n'
'${extraTabs}New gen: $newUsed used of $newCap, GC: $newFreq every $newPer\n'
'${extraTabs}Old gen: $oldUsed used of $oldCap, GC: $oldFreq every $oldPer\n'
'${extraTabs}External: $external\n';
}
Future<void> _listVMs(List<int> ports) async {
for (int port in ports) {
final VMService vmService = await _getVMService(port);
await vmService.getVM();
await vmService.refreshViews();
printStatus(_vmServiceToString(vmService));
}
}
Future<void> _validateArguments() async {
final String fuchsiaBuildDir = argResults['build-dir'];
final String gnTarget = argResults['gn-target'];
_frontendServerSnapshot = fs.file(argResults['frontend-server']);
if (!_frontendServerSnapshot.existsSync()) {
throwToolExit('Must provide a frontend-server snapshot');
}
if (fuchsiaBuildDir != null) {
if (gnTarget == null)
throwToolExit('Must provide --gn-target when specifying --build-dir.');
if (!_directoryExists(fuchsiaBuildDir))
throwToolExit('Specified --build-dir "$fuchsiaBuildDir" does not exist.');
_sshConfig = '$fuchsiaBuildDir/ssh-keys/ssh_config';
}
// If sshConfig path not available from the fuchsiaBuildDir, get from command line.
_sshConfig ??= argResults['ssh-config'];
if (_sshConfig == null)
throwToolExit('Provide the path to the ssh config file with --ssh-config.');
if (!_fileExists(_sshConfig))
throwToolExit('Couldn\'t find ssh config file at $_sshConfig.');
_address = argResults['address'];
if (_address == null && fuchsiaBuildDir != null) {
final ProcessResult result = await processManager.run(<String>['fx', 'netaddr', '--fuchsia']);
if (result.exitCode == 0)
_address = result.stdout.trim();
else
printStatus('netaddr failed:\nstdout: ${result.stdout}\nstderr: ${result.stderr}');
}
if (_address == null)
throwToolExit('Give the address of the device running Fuchsia with --address.');
_list = argResults['list'];
if (_list) {
// For --list, we only need the ssh config and device address.
return;
}
String projectRoot;
if (gnTarget != null) {
if (fuchsiaBuildDir == null)
throwToolExit('Must provide --build-dir when specifying --gn-target.');
final List<String> targetInfo = _extractPathAndName(gnTarget);
projectRoot = targetInfo[0];
_modName = targetInfo[1];
_fuchsiaProjectPath = '$fuchsiaBuildDir/../../$projectRoot';
} else if (argResults['path'] != null) {
_fuchsiaProjectPath = argResults['path'];
}
if (_fuchsiaProjectPath == null)
throwToolExit('Provide the mod project path with --path.');
if (!_directoryExists(_fuchsiaProjectPath))
throwToolExit('Cannot locate project at $_fuchsiaProjectPath.');
final String relativeTarget = argResults['target'];
if (relativeTarget == null)
throwToolExit('Give the application entry point with --target.');
_target = '$_fuchsiaProjectPath/$relativeTarget';
if (!_fileExists(_target))
throwToolExit('Couldn\'t find application entry point at $_target.');
if (argResults['mod-name'] != null)
_modName = argResults['mod-name'];
if (_modName == null)
throwToolExit('Provide the mod name with --mod-name.');
if (argResults['dot-packages'] != null) {
_dotPackagesPath = argResults['dot-packages'];
} else if (fuchsiaBuildDir != null) {
final String packagesFileName = '${_modName}_dart_library.packages';
_dotPackagesPath = '$fuchsiaBuildDir/dartlang/gen/$projectRoot/$packagesFileName';
}
if (_dotPackagesPath == null)
throwToolExit('Provide the .packages path with --dot-packages.');
if (!_fileExists(_dotPackagesPath))
throwToolExit('Couldn\'t find .packages file at $_dotPackagesPath.');
final String isolateNumber = argResults['isolate-number'];
if (isolateNumber == null) {
_isolateNumber = '';
} else {
_isolateNumber = '-$isolateNumber';
}
}
List<String> _extractPathAndName(String gnTarget) {
final String errorMessage =
'fuchsia_reload --target "$gnTarget" should have the form: '
'"//path/to/app:name"';
// Separate strings like //path/to/target:app into [path/to/target, app]
final int lastColon = gnTarget.lastIndexOf(':');
if (lastColon < 0)
throwToolExit(errorMessage);
final String name = gnTarget.substring(lastColon + 1);
// Skip '//' and chop off after :
if ((gnTarget.length < 3) || (gnTarget[0] != '/') || (gnTarget[1] != '/'))
throwToolExit(errorMessage);
final String path = gnTarget.substring(2, lastColon);
return <String>[path, name];
}
Future<List<_PortForwarder>> _forwardPorts(List<int> remotePorts) async {
final List<_PortForwarder> forwarders = <_PortForwarder>[];
for (int port in remotePorts) {
final _PortForwarder f =
await _PortForwarder.start(_sshConfig, _address, port);
forwarders.add(f);
}
return forwarders;
}
Future<List<int>> _getServicePorts() async {
final FuchsiaDeviceCommandRunner runner =
FuchsiaDeviceCommandRunner(_address, _sshConfig);
final List<String> lsOutput = await runner.run('ls /tmp/dart.services');
final List<int> ports = <int>[];
if (lsOutput != null) {
for (String s in lsOutput) {
final String trimmed = s.trim();
final int lastSpace = trimmed.lastIndexOf(' ');
final String lastWord = trimmed.substring(lastSpace + 1);
if ((lastWord != '.') && (lastWord != '..')) {
final int value = int.tryParse(lastWord);
if (value != null)
ports.add(value);
}
}
}
return ports;
}
bool _directoryExists(String path) {
final Directory d = fs.directory(path);
return d.existsSync();
}
bool _fileExists(String path) {
final File f = fs.file(path);
return f.existsSync();
}
}
// Instances of this class represent a running ssh tunnel from the host to a
// VM service running on a Fuchsia device. [process] is the ssh process running
// the tunnel and [port] is the local port.
class _PortForwarder {
_PortForwarder._(this._remoteAddress,
this._remotePort,
this._localPort,
this._process,
this._sshConfig);
final String _remoteAddress;
final int _remotePort;
final int _localPort;
final Process _process;
final String _sshConfig;
int get port => _localPort;
static Future<_PortForwarder> start(String sshConfig,
String address,
int remotePort) async {
final int localPort = await _potentiallyAvailablePort();
if (localPort == 0) {
printStatus(
'_PortForwarder failed to find a local port for $address:$remotePort');
return _PortForwarder._(null, 0, 0, null, null);
}
const String dummyRemoteCommand = 'date';
final List<String> command = <String>[
'ssh', '-F', sshConfig, '-nNT', '-vvv', '-f',
'-L', '$localPort:$ipv4Loopback:$remotePort', address, dummyRemoteCommand];
printTrace("_PortForwarder running '${command.join(' ')}'");
final Process process = await processManager.start(command);
process.stderr
.transform<String>(utf8.decoder)
.transform<String>(const LineSplitter())
.listen((String data) { printTrace(data); });
// Best effort to print the exit code.
process.exitCode.then<void>((int c) { // ignore: unawaited_futures
printTrace("'${command.join(' ')}' exited with exit code $c");
});
printTrace('Set up forwarding from $localPort to $address:$remotePort');
return _PortForwarder._(address, remotePort, localPort, process, sshConfig);
}
Future<void> stop() async {
// Kill the original ssh process if it is still around.
if (_process != null) {
printTrace('_PortForwarder killing ${_process.pid} for port $_localPort');
_process.kill();
}
// Cancel the forwarding request.
final List<String> command = <String>[
'ssh', '-F', _sshConfig, '-O', 'cancel', '-vvv',
'-L', '$_localPort:$ipv4Loopback:$_remotePort', _remoteAddress];
final ProcessResult result = await processManager.run(command);
printTrace(command.join(' '));
if (result.exitCode != 0) {
printTrace('Command failed:\nstdout: ${result.stdout}\nstderr: ${result.stderr}');
}
}
static Future<int> _potentiallyAvailablePort() async {
int port = 0;
ServerSocket s;
try {
s = await ServerSocket.bind(ipv4Loopback, 0);
port = s.port;
} catch (e) {
// Failures are signaled by a return value of 0 from this function.
printTrace('_potentiallyAvailablePort failed: $e');
}
if (s != null)
await s.close();
return port;
}
}
class FuchsiaDeviceCommandRunner {
FuchsiaDeviceCommandRunner(this._address, this._sshConfig);
final String _address;
final String _sshConfig;
Future<List<String>> run(String command) async {
final List<String> args = <String>['ssh', '-F', _sshConfig, _address, command];
printTrace(args.join(' '));
final ProcessResult result = await processManager.run(args);
if (result.exitCode != 0) {
printStatus('Command failed: $command\nstdout: ${result.stdout}\nstderr: ${result.stderr}', wrap: false);
return null;
}
printTrace(result.stdout);
return result.stdout.split('\n');
}
}
......@@ -24,6 +24,38 @@ KernelCompiler get kernelCompiler => context[KernelCompiler];
typedef CompilerMessageConsumer = void Function(String message, {bool emphasis, TerminalColor color});
/// The target model describes the set of core libraries that are availible within
/// the SDK.
class TargetModel {
/// Parse a [TargetModel] from a raw string.
///
/// Throws an [AssertionError] if passed a value other than 'flutter' or
/// 'flutter_runner'.
factory TargetModel(String rawValue) {
switch (rawValue) {
case 'flutter':
return flutter;
case 'flutter_runner':
return flutterRunner;
}
assert(false);
return null;
}
const TargetModel._(this._value);
/// The flutter patched dart SDK
static const TargetModel flutter = TargetModel._('flutter');
/// The fuchsia patched SDK.
static const TargetModel flutterRunner = TargetModel._('flutter_runner');
final String _value;
@override
String toString() => _value;
}
class CompilerOutput {
const CompilerOutput(this.outputFilename, this.errorCount);
......@@ -122,6 +154,7 @@ class KernelCompiler {
String mainPath,
String outputFilePath,
String depFilePath,
TargetModel targetModel = TargetModel.flutter,
bool linkPlatformKernelIn = false,
bool aot = false,
@required bool trackWidgetCreation,
......@@ -172,7 +205,7 @@ class KernelCompiler {
'--sdk-root',
sdkRoot,
'--strong',
'--target=flutter',
'--target=$targetModel',
];
if (trackWidgetCreation)
command.add('--track-widget-creation');
......@@ -301,12 +334,14 @@ class ResidentCompiler {
String fileSystemScheme,
CompilerMessageConsumer compilerMessageConsumer = printError,
String initializeFromDill,
TargetModel targetModel = TargetModel.flutter,
bool unsafePackageSerialization
}) : assert(_sdkRoot != null),
_trackWidgetCreation = trackWidgetCreation,
_packagesPath = packagesPath,
_fileSystemRoots = fileSystemRoots,
_fileSystemScheme = fileSystemScheme,
_targetModel = targetModel,
_stdoutHandler = _StdoutHandler(consumer: compilerMessageConsumer),
_controller = StreamController<_CompilationRequest>(),
_initializeFromDill = initializeFromDill,
......@@ -318,6 +353,7 @@ class ResidentCompiler {
final bool _trackWidgetCreation;
final String _packagesPath;
final TargetModel _targetModel;
final List<String> _fileSystemRoots;
final String _fileSystemScheme;
String _sdkRoot;
......@@ -409,7 +445,7 @@ class ResidentCompiler {
_sdkRoot,
'--incremental',
'--strong',
'--target=flutter',
'--target=$_targetModel',
];
if (outputPath != null) {
command.addAll(<String>['--output-dill', outputPath]);
......
......@@ -65,6 +65,7 @@ Future<T> runInContext<T>(
DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance,
EmulatorManager: () => EmulatorManager(),
FuchsiaSdk: () => FuchsiaSdk(),
FuchsiaArtifacts: () => FuchsiaArtifacts(),
FuchsiaWorkflow: () => FuchsiaWorkflow(),
Flags: () => const EmptyFlags(),
FlutterVersion: () => FlutterVersion(const Clock()),
......
......@@ -172,7 +172,7 @@ class FuchsiaDevice extends Device {
/// Run `command` on the Fuchsia device shell.
Future<String> shell(String command) async {
final RunResult result = await runAsync(<String>[
'ssh', '-F', fuchsiaSdk.sshConfig.absolute.path, id, command]);
'ssh', '-F', fuchsiaArtifacts.sshConfig.absolute.path, id, command]);
if (result.exitCode != 0) {
throwToolExit('Command failed: $command\nstdout: ${result.stdout}\nstderr: ${result.stderr}');
return null;
......@@ -228,7 +228,7 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
// Note: the provided command works around a bug in -N, see US-515
// for more explanation.
final List<String> command = <String>[
'ssh', '-6', '-F', fuchsiaSdk.sshConfig.absolute.path, '-nNT', '-vvv', '-f',
'ssh', '-6', '-F', fuchsiaArtifacts.sshConfig.absolute.path, '-nNT', '-vvv', '-f',
'-L', '$hostPort:$_ipv4Loopback:$devicePort', device.id, 'true'
];
final Process process = await processManager.start(command);
......@@ -252,7 +252,7 @@ class _FuchsiaPortForwarder extends DevicePortForwarder {
final Process process = _processes.remove(forwardedPort.hostPort);
process?.kill();
final List<String> command = <String>[
'ssh', '-F', fuchsiaSdk.sshConfig.absolute.path, '-O', 'cancel', '-vvv',
'ssh', '-F', fuchsiaArtifacts.sshConfig.absolute.path, '-O', 'cancel', '-vvv',
'-L', '${forwardedPort.hostPort}:$_ipv4Loopback:${forwardedPort.devicePort}', device.id];
final ProcessResult result = await processManager.run(command);
if (result.exitCode != 0) {
......
......@@ -17,6 +17,9 @@ import '../globals.dart';
/// The [FuchsiaSdk] instance.
FuchsiaSdk get fuchsiaSdk => context[FuchsiaSdk];
/// The [FuchsiaArtifacts] instance.
FuchsiaArtifacts get fuchsiaArtifacts => context[FuchsiaArtifacts];
/// The Fuchsia SDK shell commands.
///
/// This workflow assumes development within the fuchsia source tree,
......@@ -26,19 +29,6 @@ class FuchsiaSdk {
static const List<String> _netlsCommand = <String>['fx', 'netls', '--nowait'];
static const List<String> _syslogCommand = <String>['fx', 'syslog'];
/// The location of the SSH configuration file used to interact with a
/// fuchsia device.
///
/// Requires the env variable `BUILD_DIR` to be set.
File get sshConfig {
if (_sshConfig == null) {
final String buildDirectory = platform.environment['BUILD_DIR'];
_sshConfig = fs.file('$buildDirectory/ssh-keys/ssh_config');
}
return _sshConfig;
}
File _sshConfig;
/// Invokes the `netaddr` command.
///
/// This returns the network address of an attached fuchsia device. Does
......@@ -100,3 +90,26 @@ class FuchsiaSdk {
return null;
}
}
/// Fuchsia-specific artifacts used to interact with a device.
class FuchsiaArtifacts {
/// Creates a new [FuchsiaArtifacts].
///
/// May optionally provide a file `sshConfig` file.
FuchsiaArtifacts({File sshConfig})
: _sshConfig = sshConfig;
/// The location of the SSH configuration file used to interact with a
/// fuchsia device.
///
/// Requires the env variable `BUILD_DIR` to be set if not provided by
/// the constructor.
File get sshConfig {
if (_sshConfig == null) {
final String buildDirectory = platform.environment['BUILD_DIR'];
_sshConfig = fs.file('$buildDirectory/ssh-keys/ssh_config');
}
return _sshConfig;
}
File _sshConfig;
}
......@@ -35,6 +35,7 @@ class FlutterDevice {
this.fileSystemRoots,
this.fileSystemScheme,
this.viewFilter,
TargetModel targetModel = TargetModel.flutter,
ResidentCompiler generator,
}) : assert(trackWidgetCreation != null),
generator = generator ?? ResidentCompiler(
......@@ -42,6 +43,7 @@ class FlutterDevice {
trackWidgetCreation: trackWidgetCreation,
fileSystemRoots: fileSystemRoots,
fileSystemScheme: fileSystemScheme,
targetModel: targetModel,
);
final Device device;
......
......@@ -193,6 +193,23 @@ abstract class FlutterCommand extends Command<void> {
'--release or --profile; --debug always has this enabled.');
}
void usesFuchsiaOptions({bool hide = false}) {
argParser.addOption(
'target-model',
help: 'Target model that determines what core libraries are available',
defaultsTo: 'flutter',
hide: hide,
allowed: const <String>['flutter', 'flutter_runner'],
);
argParser.addOption(
'module',
abbr: 'm',
hide: hide,
help: 'The name of the module (required if attaching to a fuchsia device)',
valueHelp: 'module-name',
);
}
set defaultBuildMode(BuildMode value) {
_defaultBuildMode = value;
}
......
// Copyright 2017 The Chromium 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 'dart:convert';
import 'dart:io';
import 'package:flutter_tools/src/commands/fuchsia_reload.dart';
import 'package:mockito/mockito.dart';
import 'package:process/process.dart';
import '../src/common.dart';
import '../src/context.dart';
void main() {
group('FuchsiaDeviceCommandRunner', () {
testUsingContext('a test', () async {
final FuchsiaDeviceCommandRunner commandRunner =
FuchsiaDeviceCommandRunner('8.8.9.9',
'~/fuchsia/out/release-x86-64');
final List<String> ports = await commandRunner.run('ls /tmp');
expect(ports, hasLength(3));
expect(ports[0], equals('1234'));
expect(ports[1], equals('5678'));
expect(ports[2], equals('5'));
}, overrides: <Type, Generator>{
ProcessManager: () => MockProcessManager(),
});
});
}
class MockProcessManager extends Mock implements ProcessManager {
@override
Future<ProcessResult> run(
List<dynamic> command, {
String workingDirectory,
Map<String, String> environment,
bool includeParentEnvironment = true,
bool runInShell = false,
Encoding stdoutEncoding = systemEncoding,
Encoding stderrEncoding = systemEncoding,
}) async {
return ProcessResult(0, 0, '1234\n5678\n5', '');
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment