1// Copyright 2018 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'dart:async'; 6 7import 'package:args/args.dart'; 8 9import 'package:flutter_tools/runner.dart' as runner; 10import 'package:flutter_tools/src/artifacts.dart'; 11import 'package:flutter_tools/src/base/common.dart'; 12import 'package:flutter_tools/src/base/context.dart'; 13import 'package:flutter_tools/src/base/file_system.dart'; 14import 'package:flutter_tools/src/cache.dart'; 15import 'package:flutter_tools/src/commands/attach.dart'; 16import 'package:flutter_tools/src/commands/doctor.dart'; 17import 'package:flutter_tools/src/device.dart'; 18import 'package:flutter_tools/src/fuchsia/fuchsia_device.dart'; 19import 'package:flutter_tools/src/fuchsia/fuchsia_sdk.dart'; 20import 'package:flutter_tools/src/project.dart'; 21import 'package:flutter_tools/src/runner/flutter_command.dart'; 22 23final ArgParser parser = ArgParser() 24 ..addOption('build-dir', help: 'The fuchsia build directory') 25 ..addOption('dart-sdk', help: 'The prebuilt dart SDK') 26 ..addOption('target', help: 'The GN target to attach to') 27 ..addOption('entrypoint', defaultsTo: 'main.dart', help: 'The filename of the main method. Defaults to main.dart') 28 ..addOption('device', help: 'The device id to attach to') 29 ..addOption('dev-finder', help: 'The location of the dev_finder binary') 30 ..addFlag('verbose', negatable: true); 31 32// Track the original working directory so that the tool can find the 33// flutter repo in third_party. 34String originalWorkingDirectory; 35 36Future<void> main(List<String> args) async { 37 final ArgResults argResults = parser.parse(args); 38 final bool verbose = argResults['verbose']; 39 final String target = argResults['target']; 40 final List<String> targetParts = _extractPathAndName(target); 41 final String path = targetParts[0]; 42 final String name = targetParts[1]; 43 final File dartSdk = fs.file(argResults['dart-sdk']); 44 final String buildDirectory = argResults['build-dir']; 45 final File frontendServer = fs.file('$buildDirectory/host_x64/gen/third_party/flutter/frontend_server/frontend_server_tool.snapshot'); 46 final File sshConfig = fs.file('$buildDirectory/ssh-keys/ssh_config'); 47 final File devFinder = fs.file(argResults['dev-finder']); 48 final File platformKernelDill = fs.file('$buildDirectory/flutter_runner_patched_sdk/platform_strong.dill'); 49 final File flutterPatchedSdk = fs.file('$buildDirectory/flutter_runner_patched_sdk'); 50 final String packages = '$buildDirectory/dartlang/gen/$path/${name}_dart_library.packages'; 51 final String outputDill = '$buildDirectory/${name}_tmp.dill'; 52 53 // TODO(jonahwilliams): running from fuchsia root hangs hot reload for some reason. 54 // switch to the project root directory and run from there. 55 originalWorkingDirectory = fs.currentDirectory.path; 56 fs.currentDirectory = path; 57 58 if (!devFinder.existsSync()) { 59 print('Error: dev_finder not found at ${devFinder.path}.'); 60 return 1; 61 } 62 if (!frontendServer.existsSync()) { 63 print( 64 'Error: frontend_server not found at ${frontendServer.path}. This ' 65 'Usually means you ran fx set without specifying ' 66 '--args=flutter_profile=true.' 67 ); 68 return 1; 69 } 70 71 // Check for a package with a lib directory. 72 final String entrypoint = argResults['entrypoint']; 73 String targetFile = 'lib/$entrypoint'; 74 if (!fs.file(targetFile).existsSync()) { 75 // Otherwise assume the package is flat. 76 targetFile = entrypoint; 77 } 78 final String deviceName = argResults['device']; 79 final List<String> command = <String>[ 80 'attach', 81 '--module', 82 name, 83 '--isolate-filter', 84 name, 85 '--target', 86 targetFile, 87 '--target-model', 88 'flutter', // TODO(jonahwilliams): change to flutter_runner when dart SDK rolls 89 '--output-dill', 90 outputDill, 91 '--packages', 92 packages, 93 if (deviceName != null && deviceName.isNotEmpty) ...<String>['-d', deviceName], 94 if (verbose) '--verbose', 95 ]; 96 Cache.disableLocking(); // ignore: invalid_use_of_visible_for_testing_member 97 await runner.run( 98 command, 99 <FlutterCommand>[ 100 _FuchsiaAttachCommand(), 101 _FuchsiaDoctorCommand(), // If attach fails the tool will attempt to run doctor. 102 ], 103 verbose: verbose, 104 muteCommandLogging: false, 105 verboseHelp: false, 106 overrides: <Type, Generator>{ 107 DeviceManager: () => _FuchsiaDeviceManager(), 108 FuchsiaArtifacts: () => FuchsiaArtifacts(sshConfig: sshConfig, devFinder: devFinder), 109 Artifacts: () => OverrideArtifacts( 110 parent: CachedArtifacts(), 111 frontendServer: frontendServer, 112 engineDartBinary: dartSdk, 113 platformKernelDill: platformKernelDill, 114 flutterPatchedSdk: flutterPatchedSdk, 115 ), 116 }, 117 ); 118} 119 120// An implementation of [DeviceManager] that only supports fuchsia devices. 121class _FuchsiaDeviceManager extends DeviceManager { 122 @override 123 List<DeviceDiscovery> get deviceDiscoverers => List<DeviceDiscovery>.unmodifiable(<DeviceDiscovery>[ 124 FuchsiaDevices(), 125 ]); 126 127 @override 128 bool isDeviceSupportedForProject(Device device, FlutterProject flutterProject) { 129 return true; 130 } 131} 132 133List<String> _extractPathAndName(String gnTarget) { 134 // Separate strings like //path/to/target:app into [path/to/target, app] 135 final int lastColon = gnTarget.lastIndexOf(':'); 136 if (lastColon < 0) { 137 throwToolExit('invalid path: $gnTarget'); 138 } 139 final String name = gnTarget.substring(lastColon + 1); 140 // Skip '//' and chop off after : 141 if ((gnTarget.length < 3) || (gnTarget[0] != '/') || (gnTarget[1] != '/')) { 142 throwToolExit('invalid path: $gnTarget'); 143 } 144 final String path = gnTarget.substring(2, lastColon); 145 return <String>[path, name]; 146} 147 148class _FuchsiaDoctorCommand extends DoctorCommand { 149 @override 150 Future<FlutterCommandResult> runCommand() async { 151 Cache.flutterRoot = '$originalWorkingDirectory/third_party/dart-pkg/git/flutter'; 152 return super.runCommand(); 153 } 154} 155 156class _FuchsiaAttachCommand extends AttachCommand { 157 @override 158 Future<FlutterCommandResult> runCommand() async { 159 Cache.flutterRoot = '$originalWorkingDirectory/third_party/dart-pkg/git/flutter'; 160 return super.runCommand(); 161 } 162} 163