mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Add fuchsia specific entrypoint (#23916)
This commit is contained in:
parent
cf2fba7b3b
commit
81c7af342d
@ -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.
|
||||
}
|
||||
|
9
packages/flutter_tools/bin/fuchsia_tools.dart
Normal file
9
packages/flutter_tools/bin/fuchsia_tools.dart
Normal file
@ -0,0 +1,9 @@
|
||||
// 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(),
|
||||
|
74
packages/flutter_tools/lib/fuchsia_executable.dart
Normal file
74
packages/flutter_tools/lib/fuchsia_executable.dart
Normal file
@ -0,0 +1,74 @@
|
||||
// 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(
|
||||
|
@ -1,549 +0,0 @@
|
||||
// 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;
|
||||
}
|
||||
|
@ -1,46 +0,0 @@
|
||||
// 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', '');
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user