Skip to content

Commit 7709535

Browse files
Attach command: add Bazel filesystem support (flutter#21082)
1 parent 5e39476 commit 7709535

File tree

5 files changed

+163
-62
lines changed

5 files changed

+163
-62
lines changed

packages/flutter_tools/lib/src/commands/attach.dart

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,17 @@ final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
3737
class AttachCommand extends FlutterCommand {
3838
AttachCommand({bool verboseHelp = false, this.hotRunnerFactory}) {
3939
addBuildModeFlags(defaultToRelease: false);
40+
usesTargetOption();
41+
usesFilesystemOptions(hide: !verboseHelp);
4042
argParser
4143
..addOption(
4244
'debug-port',
4345
help: 'Local port where the observatory is listening.',
44-
)
45-
..addFlag(
46+
)..addOption(
47+
'project-root',
48+
hide: !verboseHelp,
49+
help: 'Normally used only in run target',
50+
)..addFlag(
4651
'preview-dart-2',
4752
defaultsTo: true,
4853
hide: !verboseHelp,
@@ -53,7 +58,6 @@ class AttachCommand extends FlutterCommand {
5358
help: 'Handle machine structured JSON command input and provide output\n'
5459
'and progress in machine friendly format.',
5560
);
56-
usesTargetOption();
5761
hotRunnerFactory ??= new HotRunnerFactory();
5862
}
5963

@@ -117,15 +121,23 @@ class AttachCommand extends FlutterCommand {
117121
observatoryUri = Uri.parse('http://$ipv4Loopback:$localPort/');
118122
}
119123
try {
120-
final FlutterDevice flutterDevice = new FlutterDevice(device,
121-
trackWidgetCreation: false, previewDart2: argResults['preview-dart-2']);
124+
final FlutterDevice flutterDevice = new FlutterDevice(
125+
device,
126+
trackWidgetCreation: false,
127+
previewDart2: argResults['preview-dart-2'],
128+
dillOutputPath: argResults['output-dill'],
129+
fileSystemRoots: argResults['filesystem-root'],
130+
fileSystemScheme: argResults['filesystem-scheme'],
131+
);
122132
flutterDevice.observatoryUris = <Uri>[ observatoryUri ];
123133
final HotRunner hotRunner = hotRunnerFactory.build(
124134
<FlutterDevice>[flutterDevice],
125135
target: targetFile,
126136
debuggingOptions: new DebuggingOptions.enabled(getBuildInfo()),
127137
packagesFilePath: globalResults['packages'],
128138
usesTerminalUI: daemon == null,
139+
projectRootPath: argResults['project-root'],
140+
dillOutputPath: argResults['output-dill'],
129141
);
130142

131143
if (daemon != null) {
@@ -178,4 +190,4 @@ class HotRunnerFactory {
178190
stayResident: stayResident,
179191
ipv6: ipv6,
180192
);
181-
}
193+
}

packages/flutter_tools/lib/src/commands/build_bundle.dart

Lines changed: 2 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ import 'build.dart';
1313
class BuildBundleCommand extends BuildSubCommand {
1414
BuildBundleCommand({bool verboseHelp = false}) {
1515
usesTargetOption();
16+
usesFilesystemOptions(hide: !verboseHelp);
1617
addBuildModeFlags();
1718
argParser
1819
..addFlag('precompiled', negatable: false)
@@ -58,18 +59,7 @@ class BuildBundleCommand extends BuildSubCommand {
5859
..addFlag('report-licensed-packages',
5960
help: 'Whether to report the names of all the packages that are included '
6061
'in the application\'s LICENSE file.',
61-
defaultsTo: false)
62-
..addMultiOption('filesystem-root',
63-
hide: !verboseHelp,
64-
help: 'Specify the path, that is used as root in a virtual file system\n'
65-
'for compilation. Input file name should be specified as Uri in\n'
66-
'filesystem-scheme scheme. Use only in Dart 2 mode.\n'
67-
'Requires --output-dill option to be explicitly specified.\n')
68-
..addOption('filesystem-scheme',
69-
defaultsTo: 'org-dartlang-root',
70-
hide: !verboseHelp,
71-
help: 'Specify the scheme that is used for virtual file system used in\n'
72-
'compilation. See more details on filesystem-root option.\n');
62+
defaultsTo: false);
7363
usesPubOption();
7464
}
7565

packages/flutter_tools/lib/src/commands/run.dart

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@ class RunCommand extends RunCommandBase {
8080

8181
RunCommand({ bool verboseHelp = false }) : super(verboseHelp: verboseHelp) {
8282
requiresPubspecYaml();
83+
usesFilesystemOptions(hide: !verboseHelp);
8384

8485
argParser
8586
..addFlag('start-paused',
@@ -171,23 +172,8 @@ class RunCommand extends RunCommandBase {
171172
'results out to "refresh_benchmark.json", and exit. This flag is\n'
172173
'intended for use in generating automated flutter benchmarks.',
173174
)
174-
..addOption('output-dill',
175-
hide: !verboseHelp,
176-
help: 'Specify the path to frontend server output kernel file.',
177-
)
178175
..addOption(FlutterOptions.kExtraFrontEndOptions, hide: true)
179-
..addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true)
180-
..addMultiOption('filesystem-root',
181-
hide: !verboseHelp,
182-
help: 'Specify the path, that is used as root in a virtual file system\n'
183-
'for compilation. Input file name should be specified as Uri in\n'
184-
'filesystem-scheme scheme. Use only in Dart 2 mode.\n'
185-
'Requires --output-dill option to be explicitly specified.\n')
186-
..addOption('filesystem-scheme',
187-
defaultsTo: 'org-dartlang-root',
188-
hide: !verboseHelp,
189-
help: 'Specify the scheme that is used for virtual file system used in\n'
190-
'compilation. See more details on filesystem-root option.\n');
176+
..addOption(FlutterOptions.kExtraGenSnapshotOptions, hide: true);
191177
}
192178

193179
List<Device> devices;

packages/flutter_tools/lib/src/runner/flutter_command.dart

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,31 @@ abstract class FlutterCommand extends Command<Null> {
122122
_usesPubOption = true;
123123
}
124124

125+
/// Adds flags for using a specific filesystem root and scheme.
126+
///
127+
/// [hide] indicates whether or not to hide these options when the user asks
128+
/// for help.
129+
void usesFilesystemOptions({@required bool hide}) {
130+
argParser
131+
..addOption('output-dill',
132+
hide: hide,
133+
help: 'Specify the path to frontend server output kernel file.',
134+
)
135+
..addMultiOption(FlutterOptions.kFileSystemRoot,
136+
hide: hide,
137+
help: 'Specify the path, that is used as root in a virtual file system\n'
138+
'for compilation. Input file name should be specified as Uri in\n'
139+
'filesystem-scheme scheme. Use only in Dart 2 mode.\n'
140+
'Requires --output-dill option to be explicitly specified.\n',
141+
)
142+
..addOption(FlutterOptions.kFileSystemScheme,
143+
defaultsTo: 'org-dartlang-root',
144+
hide: hide,
145+
help: 'Specify the scheme that is used for virtual file system used in\n'
146+
'compilation. See more details on filesystem-root option.\n',
147+
);
148+
}
149+
125150
void usesBuildNumberOption() {
126151
argParser.addOption('build-number',
127152
help: 'An integer used as an internal version number.\n'

packages/flutter_tools/test/commands/attach_test.dart

Lines changed: 116 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:flutter_tools/src/base/platform.dart';
1111
import 'package:flutter_tools/src/cache.dart';
1212
import 'package:flutter_tools/src/commands/attach.dart';
1313
import 'package:flutter_tools/src/device.dart';
14+
import 'package:flutter_tools/src/resident_runner.dart';
1415
import 'package:flutter_tools/src/run_hot.dart';
1516
import 'package:mockito/mockito.dart';
1617

@@ -25,48 +26,132 @@ void main() {
2526
.posix,
2627
);
2728

28-
setUpAll(() {
29+
setUp(() {
2930
Cache.disableLocking();
3031
testFileSystem.directory('lib').createSync();
3132
testFileSystem.file('lib/main.dart').createSync();
3233
});
3334

34-
testUsingContext('finds observatory port and forwards', () async {
35+
group('with one device and no specified target file', () {
3536
const int devicePort = 499;
3637
const int hostPort = 42;
37-
final MockDeviceLogReader mockLogReader = new MockDeviceLogReader();
38-
final MockPortForwarder portForwarder = new MockPortForwarder();
39-
final MockAndroidDevice device = new MockAndroidDevice();
40-
when(device.getLogReader()).thenAnswer((_) {
41-
// Now that the reader is used, start writing messages to it.
42-
Timer.run(() {
43-
mockLogReader.addLine('Foo');
44-
mockLogReader.addLine(
45-
'Observatory listening on http://127.0.0.1:$devicePort');
38+
39+
MockDeviceLogReader mockLogReader;
40+
MockPortForwarder portForwarder;
41+
MockAndroidDevice device;
42+
43+
setUp(() {
44+
mockLogReader = new MockDeviceLogReader();
45+
portForwarder = new MockPortForwarder();
46+
device = new MockAndroidDevice();
47+
when(device.getLogReader()).thenAnswer((_) {
48+
// Now that the reader is used, start writing messages to it.
49+
Timer.run(() {
50+
mockLogReader.addLine('Foo');
51+
mockLogReader.addLine(
52+
'Observatory listening on http://127.0.0.1:$devicePort');
53+
});
54+
55+
return mockLogReader;
4656
});
57+
when(device.portForwarder).thenReturn(portForwarder);
58+
when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
59+
.thenAnswer((_) async => hostPort);
60+
when(portForwarder.forwardedPorts).thenReturn(
61+
<ForwardedPort>[new ForwardedPort(hostPort, devicePort)]);
62+
when(portForwarder.unforward(any)).thenAnswer((_) async => null);
4763

48-
return mockLogReader;
64+
// We cannot add the device to a device manager because that is
65+
// only enabled by the context of each testUsingContext call.
66+
//
67+
// Instead each test will add the device to the device manager
68+
// on its own.
4969
});
50-
when(device.portForwarder).thenReturn(portForwarder);
51-
when(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
52-
.thenAnswer((_) async => hostPort);
53-
when(portForwarder.forwardedPorts).thenReturn(
54-
<ForwardedPort>[new ForwardedPort(hostPort, devicePort)]);
55-
when(portForwarder.unforward(any)).thenAnswer((_) async => null);
56-
testDeviceManager.addDevice(device);
5770

58-
final AttachCommand command = new AttachCommand();
71+
tearDown(() {
72+
mockLogReader.dispose();
73+
});
5974

60-
await createTestCommandRunner(command).run(<String>['attach']);
75+
testUsingContext('finds observatory port and forwards', () async {
76+
testDeviceManager.addDevice(device);
6177

62-
verify(portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')))
63-
.called(1);
78+
final AttachCommand command = new AttachCommand();
79+
80+
await createTestCommandRunner(command).run(<String>['attach']);
81+
82+
verify(
83+
portForwarder.forward(devicePort, hostPort: anyNamed('hostPort')),
84+
).called(1);
85+
}, overrides: <Type, Generator>{
86+
FileSystem: () => testFileSystem,
87+
});
88+
89+
testUsingContext('accepts filesystem parameters', () async {
90+
testDeviceManager.addDevice(device);
91+
92+
const String filesystemScheme = 'foo';
93+
const String filesystemRoot = '/build-output/';
94+
const String projectRoot = '/build-output/project-root';
95+
const String outputDill = '/tmp/output.dill';
96+
97+
final MockHotRunnerFactory mockHotRunnerFactory = new MockHotRunnerFactory();
98+
when(
99+
mockHotRunnerFactory.build(
100+
any,
101+
target: anyNamed('target'),
102+
projectRootPath: anyNamed('projectRootPath'),
103+
dillOutputPath: anyNamed('dillOutputPath'),
104+
debuggingOptions: anyNamed('debuggingOptions'),
105+
packagesFilePath: anyNamed('packagesFilePath'),
106+
usesTerminalUI: anyNamed('usesTerminalUI'),
107+
),
108+
)..thenReturn(new MockHotRunner());
109+
110+
final AttachCommand command = new AttachCommand(
111+
hotRunnerFactory: mockHotRunnerFactory,
112+
);
113+
await createTestCommandRunner(command).run(<String>[
114+
'attach',
115+
'--filesystem-scheme',
116+
filesystemScheme,
117+
'--filesystem-root',
118+
filesystemRoot,
119+
'--project-root',
120+
projectRoot,
121+
'--output-dill',
122+
outputDill,
123+
'-v',
124+
]);
125+
126+
// Validate the attach call built a mock runner with the right
127+
// project root and output dill.
128+
final VerificationResult verificationResult = verify(
129+
mockHotRunnerFactory.build(
130+
captureAny,
131+
target: anyNamed('target'),
132+
projectRootPath: projectRoot,
133+
dillOutputPath: outputDill,
134+
debuggingOptions: anyNamed('debuggingOptions'),
135+
packagesFilePath: anyNamed('packagesFilePath'),
136+
usesTerminalUI: anyNamed('usesTerminalUI'),
137+
),
138+
)..called(1);
139+
140+
final List<FlutterDevice> flutterDevices = verificationResult.captured.first;
141+
expect(flutterDevices, hasLength(1));
142+
143+
// Validate that the attach call built a flutter device with the right
144+
// output dill, filesystem scheme, and filesystem root.
145+
final FlutterDevice flutterDevice = flutterDevices.first;
146+
147+
expect(flutterDevice.dillOutputPath, outputDill);
148+
expect(flutterDevice.fileSystemScheme, filesystemScheme);
149+
expect(flutterDevice.fileSystemRoots, const <String>[filesystemRoot]);
150+
}, overrides: <Type, Generator>{
151+
FileSystem: () => testFileSystem,
152+
});
153+
});
64154

65-
mockLogReader.dispose();
66-
}, overrides: <Type, Generator>{
67-
FileSystem: () => testFileSystem,
68-
},
69-
);
70155

71156
testUsingContext('selects specified target', () async {
72157
const int devicePort = 499;
@@ -102,6 +187,9 @@ void main() {
102187
final File foo = fs.file('lib/foo.dart')
103188
..createSync();
104189

190+
// Delete the main.dart file to be sure that attach works without it.
191+
fs.file('lib/main.dart').deleteSync();
192+
105193
final AttachCommand command = new AttachCommand(
106194
hotRunnerFactory: mockHotRunnerFactory);
107195
await createTestCommandRunner(command).run(

0 commit comments

Comments
 (0)