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: 1cf3907407/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): 1cf3907407/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: 1cf3907407/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: 1cf3907407/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: 1cf3907407/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()`](1cf3907407/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.
This commit is contained in:
Christopher Fujino 2023-08-10 10:51:05 -07:00 committed by GitHub
parent 64a0683b41
commit a6118612ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 91 additions and 61 deletions

View File

@ -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,

View File

@ -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,15 +70,14 @@ class AttachCommand extends FlutterCommand {
required Platform platform,
required ProcessInfo processInfo,
required FileSystem fileSystem,
}): _artifacts = artifacts,
_hotRunnerFactory = hotRunnerFactory ?? HotRunnerFactory(),
_stdio = stdio,
_logger = logger,
_terminal = terminal,
_signals = signals,
_platform = platform,
_processInfo = processInfo,
_fileSystem = fileSystem {
}) : _hotRunnerFactory = hotRunnerFactory ?? HotRunnerFactory(),
_stdio = stdio,
_logger = logger,
_terminal = terminal,
_signals = signals,
_platform = platform,
_processInfo = processInfo,
_fileSystem = fileSystem {
addBuildModeFlags(verboseHelp: verboseHelp, defaultToRelease: false, excludeRelease: true);
usesTargetOption();
usesPortOptions(verboseHelp: verboseHelp);
@ -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();
}

View File

@ -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',

View File

@ -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

View File

@ -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';

View File

@ -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(),