diff --git a/packages/flutter_tools/BUILD.gn b/packages/flutter_tools/BUILD.gn index 1fcfbe21d44..d5df764e5c6 100644 --- a/packages/flutter_tools/BUILD.gn +++ b/packages/flutter_tools/BUILD.gn @@ -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. +} diff --git a/packages/flutter_tools/bin/fuchsia_tools.dart b/packages/flutter_tools/bin/fuchsia_tools.dart new file mode 100644 index 00000000000..9a0867e6d82 --- /dev/null +++ b/packages/flutter_tools/bin/fuchsia_tools.dart @@ -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 args) { + executable.main(args); +} diff --git a/packages/flutter_tools/lib/executable.dart b/packages/flutter_tools/lib/executable.dart index d8c8c1ab6c9..bf95f0c0faf 100644 --- a/packages/flutter_tools/lib/executable.dart +++ b/packages/flutter_tools/lib/executable.dart @@ -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 main(List args) async { DriveCommand(), EmulatorsCommand(), FormatCommand(), - FuchsiaReloadCommand(), IdeConfigCommand(hidden: !verboseHelp), InjectPluginsCommand(hidden: !verboseHelp), InstallCommand(), diff --git a/packages/flutter_tools/lib/fuchsia_executable.dart b/packages/flutter_tools/lib/fuchsia_executable.dart new file mode 100644 index 00000000000..51d5d2ba33a --- /dev/null +++ b/packages/flutter_tools/lib/fuchsia_executable.dart @@ -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 main(List 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, [ + AttachCommand(verboseHelp: verboseHelp), + DevicesCommand(), + ShellCompletionCommand(), + ], verbose: verbose, + muteCommandLogging: help, + verboseHelp: verboseHelp, + overrides: { + FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig), + Artifacts: () => OverrideArtifacts( + parent: CachedArtifacts(), + frontendServer: frontendServer, + engineDartBinary: dartSdk, + ) + }); +} diff --git a/packages/flutter_tools/lib/runner.dart b/packages/flutter_tools/lib/runner.dart index 963d801d57f..fdcca4b5d4e 100644 --- a/packages/flutter_tools/lib/runner.dart +++ b/packages/flutter_tools/lib/runner.dart @@ -34,6 +34,7 @@ Future run( bool verboseHelp = false, bool reportCrashes, String flutterVersion, + Map overrides, }) { reportCrashes ??= !isRunningOnBot; @@ -63,7 +64,7 @@ Future run( return await _handleToolError(error, stackTrace, verbose, args, reportCrashes, getVersion); } return 0; - }); + }, overrides: overrides); } Future _handleToolError( diff --git a/packages/flutter_tools/lib/src/artifacts.dart b/packages/flutter_tools/lib/src/artifacts.dart index 39bd512d045..0fa9df2bdd8 100644 --- a/packages/flutter_tools/lib/src/artifacts.dart +++ b/packages/flutter_tools/lib/src/artifacts.dart @@ -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); } diff --git a/packages/flutter_tools/lib/src/commands/attach.dart b/packages/flutter_tools/lib/src/commands/attach.dart index dc52a6cfdbb..b2282c1f789 100644 --- a/packages/flutter_tools/lib/src/commands/attach.dart +++ b/packages/flutter_tools/lib/src/commands/attach.dart @@ -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 = [ observatoryUri ]; final HotRunner hotRunner = hotRunnerFactory.build( diff --git a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart b/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart deleted file mode 100644 index 7dc1a888c7f..00000000000 --- a/packages/flutter_tools/lib/src/commands/fuchsia_reload.dart +++ /dev/null @@ -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 runCommand() async { - Cache.releaseLockEarly(); - await _validateArguments(); - await runInContext(() async { - // Find the network ports used on the device by VM service instances. - final List 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 servicePorts = forwardedPorts.map((_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 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 fullAddresses = - targetPorts.map((int p) => '$ipv4Loopback:$p').toList(); - final List observatoryUris = fullAddresses - .map((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], - debuggingOptions: DebuggingOptions.enabled(getBuildInfo()), - target: _target, - projectRootPath: _fuchsiaProjectPath, - packagesFilePath: _dotPackagesPath, - ); - printStatus('Connecting to $_modName'); - await hotRunner.attach(); - } finally { - await Future.wait(forwardedPorts.map>((_PortForwarder pf) => pf.stop())); - } - }, overrides: { - Artifacts: () => OverrideArtifacts(parent: artifacts, frontendServer: _frontendServerSnapshot), - }); - return null; - } - - // A cache of VMService connections. - final HashMap _vmServiceCache = HashMap(); - - Future _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> _getViews(List ports) async { - final List views = []; - 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> _filterPorts(List ports, String viewFilter) async { - printTrace('Looing for view $viewFilter'); - final List result = []; - 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 _listVMs(List ports) async { - for (int port in ports) { - final VMService vmService = await _getVMService(port); - await vmService.getVM(); - await vmService.refreshViews(); - printStatus(_vmServiceToString(vmService)); - } - } - - Future _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(['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 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 _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 [path, name]; - } - - Future> _forwardPorts(List 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> _getServicePorts() async { - final FuchsiaDeviceCommandRunner runner = - FuchsiaDeviceCommandRunner(_address, _sshConfig); - final List lsOutput = await runner.run('ls /tmp/dart.services'); - final List ports = []; - 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 command = [ - '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(utf8.decoder) - .transform(const LineSplitter()) - .listen((String data) { printTrace(data); }); - // Best effort to print the exit code. - process.exitCode.then((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 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 command = [ - '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 _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> run(String command) async { - final List args = ['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'); - } -} diff --git a/packages/flutter_tools/lib/src/compile.dart b/packages/flutter_tools/lib/src/compile.dart index 2c83f773bcd..fa8f42c55e9 100644 --- a/packages/flutter_tools/lib/src/compile.dart +++ b/packages/flutter_tools/lib/src/compile.dart @@ -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 _fileSystemRoots; final String _fileSystemScheme; String _sdkRoot; @@ -409,7 +445,7 @@ class ResidentCompiler { _sdkRoot, '--incremental', '--strong', - '--target=flutter', + '--target=$_targetModel', ]; if (outputPath != null) { command.addAll(['--output-dill', outputPath]); diff --git a/packages/flutter_tools/lib/src/context_runner.dart b/packages/flutter_tools/lib/src/context_runner.dart index afa56d40006..f9735162b23 100644 --- a/packages/flutter_tools/lib/src/context_runner.dart +++ b/packages/flutter_tools/lib/src/context_runner.dart @@ -65,6 +65,7 @@ Future runInContext( DoctorValidatorsProvider: () => DoctorValidatorsProvider.defaultInstance, EmulatorManager: () => EmulatorManager(), FuchsiaSdk: () => FuchsiaSdk(), + FuchsiaArtifacts: () => FuchsiaArtifacts(), FuchsiaWorkflow: () => FuchsiaWorkflow(), Flags: () => const EmptyFlags(), FlutterVersion: () => FlutterVersion(const Clock()), diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart index b47557892ca..42ea084d7fb 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_device.dart @@ -172,7 +172,7 @@ class FuchsiaDevice extends Device { /// Run `command` on the Fuchsia device shell. Future shell(String command) async { final RunResult result = await runAsync([ - '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 command = [ - '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 command = [ - '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) { diff --git a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart index 505ab10c1fe..a67ea946aa2 100644 --- a/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart +++ b/packages/flutter_tools/lib/src/fuchsia/fuchsia_sdk.dart @@ -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 _netlsCommand = ['fx', 'netls', '--nowait']; static const List _syslogCommand = ['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; +} diff --git a/packages/flutter_tools/lib/src/resident_runner.dart b/packages/flutter_tools/lib/src/resident_runner.dart index 6bb66a707be..4fdee10e4e7 100644 --- a/packages/flutter_tools/lib/src/resident_runner.dart +++ b/packages/flutter_tools/lib/src/resident_runner.dart @@ -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; diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart index e2b87485114..95ae66fa04e 100644 --- a/packages/flutter_tools/lib/src/runner/flutter_command.dart +++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart @@ -193,6 +193,23 @@ abstract class FlutterCommand extends Command { '--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 ['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; } diff --git a/packages/flutter_tools/test/commands/fuchsia_reload_test.dart b/packages/flutter_tools/test/commands/fuchsia_reload_test.dart deleted file mode 100644 index 1c32964c86b..00000000000 --- a/packages/flutter_tools/test/commands/fuchsia_reload_test.dart +++ /dev/null @@ -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 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: { - ProcessManager: () => MockProcessManager(), - }); - }); -} - -class MockProcessManager extends Mock implements ProcessManager { - @override - Future run( - List command, { - String workingDirectory, - Map environment, - bool includeParentEnvironment = true, - bool runInShell = false, - Encoding stdoutEncoding = systemEncoding, - Encoding stderrEncoding = systemEncoding, - }) async { - return ProcessResult(0, 0, '1234\n5678\n5', ''); - } -}