Unverified Commit a6118612 authored by Christopher Fujino's avatar Christopher Fujino Committed by GitHub

Fix flutter attach local engine (#131825)

Fixes: https://github.com/flutter/flutter/issues/124970
Part of https://github.com/flutter/flutter/issues/47161

Before this change, there were two places we overrode the `Artifacts` in a Zone:

1. if/when we parse local-engine CLI options: https://github.com/flutter/flutter/blob/1cf3907407cbc91be7cec2c38b348a2d66041dd5/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart#L281
2. an additional override for fuchsia platform dill (no longer used, deleted in this PR): https://github.com/flutter/flutter/blob/1cf3907407cbc91be7cec2c38b348a2d66041dd5/packages/flutter_tools/lib/src/commands/attach.dart#L274

Note 1 above creates a new instance of `Artifacts.getLocalEngine()`. In this flow, there exist two instances of `Artifacts`:

1. The default fallback instance of `CachedArtifacts` (which gets all artifacts from flutter/bin/cache), instantiated in context_runner.dart: https://github.com/flutter/flutter/blob/1cf3907407cbc91be7cec2c38b348a2d66041dd5/packages/flutter_tools/lib/src/context_runner.dart#L137
2. An instance of `CachedLocalEngineArtifacts` created in the command runner once the CLI options have been parsed: https://github.com/flutter/flutter/blob/1cf3907407cbc91be7cec2c38b348a2d66041dd5/packages/flutter_tools/lib/src/runner/flutter_command_runner.dart#L281

The regression happened when we direct injected the Artifacts 1 from above BEFORE we parsed the local-engine flag, and then used this in the second zone override, and then when creating the `FlutterDevice` there are multiple calls to `globals.artifacts` returned it when it should have returned Artifacts 2: https://github.com/flutter/flutter/blob/1cf3907407cbc91be7cec2c38b348a2d66041dd5/packages/flutter_tools/lib/src/resident_runner.dart#L80

Device.artifactOverrides was originally introduced in https://github.com/flutter/flutter/pull/32071, but is no longer used, so I deleted it.

I also removed direct injection of `Artifacts` to the attach sub-command, because that class now no longer references artifacts.

I believe the ideal true fix for this would be to:

1. Migrate all leaf calls to `globals.artifacts` to use direct injection (in this case, the offending invocations were in [`FlutterDevice.create()`](https://github.com/flutter/flutter/blob/1cf3907407cbc91be7cec2c38b348a2d66041dd5/packages/flutter_tools/lib/src/resident_runner.dart#L80-L218), but I'm not sure that something else would not have broken later)
2. Ensure we are always direct injecting the desired instance of `Artifacts`--that is, if the user desires local engine artifacts, that we are passing an instance of `CachedLocalEngineArtifacts`.
  a. Alternatively, and probably simpler, teach `CachedArtifacts` to know about the local engine. This would mean parsing the global CLI options BEFORE we ever construct any instance of `Artifacts`.
  
As an overall recommendation for implementing https://github.com/flutter/flutter/issues/47161, in the overall tree of tool function calls, we should probably migrate the leaves first (that is, migrate the sub-commands last). We should also audit and reconsider any usage of `runZoned()` or `context.run()` for the purpose overriding zoneValues.
parent 64a0683b
......@@ -156,7 +156,6 @@ List<FlutterCommand> generateCommands({
AssembleCommand(verboseHelp: verboseHelp, buildSystem: globals.buildSystem),
AttachCommand(
verboseHelp: verboseHelp,
artifacts: globals.artifacts,
stdio: globals.stdio,
logger: globals.logger,
terminal: globals.terminal,
......
......@@ -7,9 +7,7 @@ import 'dart:async';
import 'package:vm_service/vm_service.dart';
import '../android/android_device.dart';
import '../artifacts.dart';
import '../base/common.dart';
import '../base/context.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
......@@ -65,7 +63,6 @@ class AttachCommand extends FlutterCommand {
AttachCommand({
bool verboseHelp = false,
HotRunnerFactory? hotRunnerFactory,
required Artifacts? artifacts,
required Stdio stdio,
required Logger logger,
required Terminal terminal,
......@@ -73,8 +70,7 @@ class AttachCommand extends FlutterCommand {
required Platform platform,
required ProcessInfo processInfo,
required FileSystem fileSystem,
}): _artifacts = artifacts,
_hotRunnerFactory = hotRunnerFactory ?? HotRunnerFactory(),
}) : _hotRunnerFactory = hotRunnerFactory ?? HotRunnerFactory(),
_stdio = stdio,
_logger = logger,
_terminal = terminal,
......@@ -145,7 +141,6 @@ class AttachCommand extends FlutterCommand {
}
final HotRunnerFactory _hotRunnerFactory;
final Artifacts? _artifacts;
final Stdio _stdio;
final Logger _logger;
final Terminal _terminal;
......@@ -267,13 +262,7 @@ known, it can be explicitly provided to attach via the command-line, e.g.
throwToolExit('Did not find any valid target devices.');
}
final Artifacts? overrideArtifacts = device.artifactOverrides ?? _artifacts;
await context.run<void>(
body: () => _attachToDevice(device),
overrides: <Type, Generator>{
Artifacts: () => overrideArtifacts,
},
);
await _attachToDevice(device);
return FlutterCommandResult.success();
}
......
......@@ -585,7 +585,7 @@ class DefaultResidentCompiler implements ResidentCompiler {
required this.buildMode,
required Logger logger,
required ProcessManager processManager,
required Artifacts artifacts,
required this.artifacts,
required Platform platform,
required FileSystem fileSystem,
this.testCompilation = false,
......@@ -604,7 +604,6 @@ class DefaultResidentCompiler implements ResidentCompiler {
@visibleForTesting StdoutHandler? stdoutHandler,
}) : _logger = logger,
_processManager = processManager,
_artifacts = artifacts,
_stdoutHandler = stdoutHandler ?? StdoutHandler(logger: logger, fileSystem: fileSystem),
_platform = platform,
dartDefines = dartDefines ?? const <String>[],
......@@ -615,7 +614,7 @@ class DefaultResidentCompiler implements ResidentCompiler {
final Logger _logger;
final ProcessManager _processManager;
final Artifacts _artifacts;
final Artifacts artifacts;
final Platform _platform;
final bool testCompilation;
......@@ -751,12 +750,12 @@ class DefaultResidentCompiler implements ResidentCompiler {
{String? additionalSourceUri}
) async {
final TargetPlatform? platform = (targetModel == TargetModel.dartdevc) ? TargetPlatform.web_javascript : null;
final String frontendServer = _artifacts.getArtifactPath(
final String frontendServer = artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk,
platform: platform,
);
final List<String> command = <String>[
_artifacts.getArtifactPath(Artifact.engineDartBinary, platform: platform),
artifacts.getArtifactPath(Artifact.engineDartBinary, platform: platform),
'--disable-dart-dev',
frontendServer,
'--sdk-root',
......
......@@ -8,7 +8,6 @@ import 'dart:math' as math;
import 'package:meta/meta.dart';
import 'application_package.dart';
import 'artifacts.dart';
import 'base/context.dart';
import 'base/dds.dart';
import 'base/file_system.dart';
......@@ -741,9 +740,6 @@ abstract class Device {
/// Clear the device's logs.
void clearLogs();
/// Optional device-specific artifact overrides.
OverrideArtifacts? get artifactOverrides => null;
/// Start an app package on the current device.
///
/// [platformArgs] allows callers to pass platform-specific arguments to the
......
......@@ -19,6 +19,7 @@ import 'package:flutter_tools/src/base/terminal.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/cache.dart';
import 'package:flutter_tools/src/commands/attach.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:flutter_tools/src/device.dart';
import 'package:flutter_tools/src/device_port_forwarder.dart';
import 'package:flutter_tools/src/ios/application_package.dart';
......@@ -72,12 +73,12 @@ void main() {
testFileSystem = MemoryFileSystem.test();
testFileSystem.directory('lib').createSync();
testFileSystem.file(testFileSystem.path.join('lib', 'main.dart')).createSync();
artifacts = Artifacts.test();
artifacts = Artifacts.test(fileSystem: testFileSystem);
stdio = FakeStdio();
terminal = FakeTerminal();
signals = Signals.test();
processInfo = FakeProcessInfo();
testDeviceManager = TestDeviceManager(logger: BufferLogger.test());
testDeviceManager = TestDeviceManager(logger: logger);
});
group('with one device and no specified target file', () {
......@@ -135,7 +136,6 @@ void main() {
await createTestCommandRunner(AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -199,7 +199,6 @@ void main() {
await createTestCommandRunner(AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -229,6 +228,75 @@ void main() {
Signals: () => FakeSignals(),
});
testUsingContext('local engine artifacts are passed to runner', () async {
const String localEngineSrc = '/path/to/local/engine/src';
const String localEngineDir = 'host_debug_unopt';
testFileSystem.directory('$localEngineSrc/out/$localEngineDir').createSync(recursive: true);
final FakeIOSDevice device = FakeIOSDevice(
portForwarder: portForwarder,
majorSdkVersion: 12,
onGetLogReader: () {
fakeLogReader.addLine('Foo');
fakeLogReader.addLine('The Dart VM service is listening on http://127.0.0.1:$devicePort');
return fakeLogReader;
},
);
testDeviceManager.devices = <Device>[device];
final Completer<void> completer = Completer<void>();
final StreamSubscription<String> loggerSubscription = logger.stream.listen((String message) {
if (message == '[verbose] VM Service URL on device: http://127.0.0.1:$devicePort') {
// The "VM Service URL on device" message is output by the ProtocolDiscovery when it found the VM Service.
completer.complete();
}
});
final FakeHotRunner hotRunner = FakeHotRunner();
hotRunner.onAttach = (
Completer<DebugConnectionInfo>? connectionInfoCompleter,
Completer<void>? appStartedCompleter,
bool allowExistingDdsInstance,
bool enableDevTools,
) async => 0;
hotRunner.exited = false;
hotRunner.isWaitingForVmService = false;
bool passedArtifactTest = false;
final FakeHotRunnerFactory hotRunnerFactory = FakeHotRunnerFactory()
..hotRunner = hotRunner
.._artifactTester = (Artifacts artifacts) {
expect(artifacts, isA<CachedLocalEngineArtifacts>());
// expecting this to be true ensures this test ran
passedArtifactTest = true;
};
await createTestCommandRunner(AttachCommand(
hotRunnerFactory: hotRunnerFactory,
stdio: stdio,
logger: logger,
terminal: terminal,
signals: signals,
platform: platform,
processInfo: processInfo,
fileSystem: testFileSystem,
)).run(<String>['attach', '--local-engine-src-path=$localEngineSrc', '--local-engine=$localEngineDir']);
await Future.wait<void>(<Future<void>>[
completer.future,
fakeLogReader.dispose(),
loggerSubscription.cancel(),
]);
expect(passedArtifactTest, isTrue);
}, overrides: <Type, Generator>{
Artifacts: () => artifacts,
DeviceManager: () => testDeviceManager,
FileSystem: () => testFileSystem,
Logger: () => logger,
MDnsVmServiceDiscovery: () => MDnsVmServiceDiscovery(
mdnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}),
preliminaryMDnsClient: FakeMDnsClient(<PtrResourceRecord>[], <String, List<SrvResourceRecord>>{}),
logger: logger,
flutterUsage: TestUsage(),
),
ProcessManager: () => FakeProcessManager.empty(),
});
testUsingContext('succeeds with iOS device with mDNS', () async {
final FakeIOSDevice device = FakeIOSDevice(
portForwarder: portForwarder,
......@@ -254,7 +322,6 @@ void main() {
await createTestCommandRunner(AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -319,7 +386,6 @@ void main() {
await createTestCommandRunner(AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -390,7 +456,6 @@ void main() {
await createTestCommandRunner(AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -465,7 +530,6 @@ void main() {
await createTestCommandRunner(AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -534,7 +598,6 @@ void main() {
}
});
final Future<void> task = createTestCommandRunner(AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -567,7 +630,6 @@ void main() {
};
testDeviceManager.devices = <Device>[device];
expect(() => createTestCommandRunner(AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -611,7 +673,6 @@ void main() {
final AttachCommand command = AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -655,7 +716,6 @@ void main() {
testDeviceManager.devices = <Device>[device];
final AttachCommand command = AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -709,7 +769,6 @@ void main() {
await createTestCommandRunner(AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -741,7 +800,6 @@ void main() {
testDeviceManager.devices = <Device>[device];
final AttachCommand command = AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -790,7 +848,6 @@ void main() {
}
});
final Future<void> task = createTestCommandRunner(AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -825,7 +882,6 @@ void main() {
}
});
final Future<void> task = createTestCommandRunner(AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -861,7 +917,6 @@ void main() {
}
});
final Future<void> task = createTestCommandRunner(AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -906,7 +961,6 @@ void main() {
}
});
final Future<void> task = createTestCommandRunner(AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -943,7 +997,6 @@ void main() {
testUsingContext('exits when no device connected', () async {
final AttachCommand command = AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -967,7 +1020,6 @@ void main() {
final FakeIOSDevice device = FakeIOSDevice();
testDeviceManager.devices = <Device>[device];
expect(createTestCommandRunner(AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -988,7 +1040,6 @@ void main() {
testUsingContext('exits when multiple devices connected', () async {
final AttachCommand command = AttachCommand(
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -1038,7 +1089,6 @@ void main() {
final AttachCommand command = AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -1079,7 +1129,6 @@ void main() {
final AttachCommand command = AttachCommand(
hotRunnerFactory: hotRunnerFactory,
artifacts: artifacts,
stdio: stdio,
logger: logger,
terminal: terminal,
......@@ -1134,6 +1183,7 @@ class FakeHotRunnerFactory extends Fake implements HotRunnerFactory {
String? dillOutputPath;
String? projectRootPath;
late List<FlutterDevice> devices;
void Function(Artifacts artifacts)? _artifactTester;
@override
HotRunner build(
......@@ -1150,6 +1200,11 @@ class FakeHotRunnerFactory extends Fake implements HotRunnerFactory {
bool ipv6 = false,
FlutterProject? flutterProject,
}) {
if (_artifactTester != null) {
for (final FlutterDevice device in devices) {
_artifactTester!((device.generator! as DefaultResidentCompiler).artifacts);
}
}
this.devices = devices;
this.dillOutputPath = dillOutputPath;
this.projectRootPath = projectRootPath;
......@@ -1399,9 +1454,6 @@ class FakeAndroidDevice extends Fake implements AndroidDevice {
return onGetLogReader!();
}
@override
OverrideArtifacts? get artifactOverrides => null;
@override
final PlatformType platformType = PlatformType.android;
......@@ -1456,9 +1508,6 @@ class FakeIOSDevice extends Fake implements IOSDevice {
return onGetLogReader!();
}
@override
OverrideArtifacts? get artifactOverrides => null;
@override
final String name = 'name';
......
......@@ -5,7 +5,6 @@
import 'package:args/args.dart';
import 'package:args/command_runner.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/file_system.dart';
import 'package:flutter_tools/src/base/io.dart';
import 'package:flutter_tools/src/base/logger.dart';
......@@ -75,7 +74,6 @@ void main() {
),
),
AttachCommand(
artifacts: Artifacts.test(),
stdio: FakeStdio(),
logger: logger,
terminal: FakeTerminal(),
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment