[ Widget Preview ] Add support for dart:ffi imports (#180586)

Adds logic to pass `--include-unsupported-platform-library-stubs` to the
CFE when launched via `flutter widget-preview start`, as well as logic
to ensure the flag can't be passed as an extra frontend option to bypass
compile time errors due to imports of unsupported libraries.

Fixes https://github.com/flutter/flutter/issues/166431
This commit is contained in:
Ben Konyi 2026-01-06 20:47:22 -05:00 committed by GitHub
parent afdf7e2029
commit 15c48f2922
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 237 additions and 3 deletions

View File

@ -220,6 +220,8 @@ template("_compile_platform") {
skwasm_library = "dart:_skwasm_stub"
if (invoker.kernel_target == "dart2wasm") {
skwasm_library = "dart:_skwasm_impl"
} else if (invoker.kernel_target == "ddc") {
args += [ "--include-unsupported-platform-library-stubs" ]
}
args += [

View File

@ -53,6 +53,7 @@ class BuildInfo {
this.assumeInitializeFromDillUpToDate = false,
this.buildNativeAssets = true,
this.useLocalCanvasKit = false,
this.includeUnsupportedPlatformLibraryStubs = false,
this.webEnableHotReload = false,
}) : extraFrontEndOptions = extraFrontEndOptions ?? const <String>[],
extraGenSnapshotOptions = extraGenSnapshotOptions ?? const <String>[],
@ -181,6 +182,11 @@ class BuildInfo {
/// If set, builds native assets with `build.dart` from all packages.
final bool buildNativeAssets;
/// If set, unsupported core libraries can be imported without causing a compile time error.
///
/// This should only be used by developer tooling as unsupported APIs will throw at runtime.
final bool includeUnsupportedPlatformLibraryStubs;
/// If set, web builds will use the locally built CanvasKit instead of using the CDN
final bool useLocalCanvasKit;

View File

@ -481,6 +481,7 @@ final class WidgetPreviewStartCommand extends WidgetPreviewSubCommandBase with C
// Don't try and download canvaskit from the CDN.
useLocalCanvasKit: true,
webEnableHotReload: true,
includeUnsupportedPlatformLibraryStubs: true,
),
webEnableExposeUrl: false,
webEnableExpressionEvaluation: true,

View File

@ -207,6 +207,16 @@ List<String> buildModeOptions(BuildMode mode, List<String> dartDefines) => switc
_ => throw Exception('Unknown BuildMode: $mode'),
};
final _extraFrontEndOptionsFilterSet = <String>{
// Don't allow for users to pass --include-unsupported-platform-library-stubs to bypass
// unsupported library compile time errors as this flag is only safe for use by developer
// tooling.
'--include-unsupported-platform-library-stubs',
};
Iterable<String>? _filterExtraFrontEndOptions(List<String>? options) =>
options?.where((e) => !_extraFrontEndOptionsFilterSet.contains(e));
/// A compiler interface for producing single (non-incremental) kernel files.
class KernelCompiler {
KernelCompiler({
@ -371,7 +381,7 @@ class KernelCompiler {
if (nativeAssets != null) ...<String>['--native-assets', nativeAssets],
// See: https://github.com/flutter/flutter/issues/103994
'--verbosity=error',
...?extraFrontEndOptions,
...?_filterExtraFrontEndOptions(extraFrontEndOptions),
mainUri ?? '--native-assets-only',
];
@ -509,6 +519,7 @@ abstract class ResidentCompiler {
required ShutdownHooks shutdownHooks,
bool testCompilation,
bool trackWidgetCreation,
bool includeUnsupportedPlatformLibraryStubs,
String packagesPath,
List<String> fileSystemRoots,
String? fileSystemScheme,
@ -631,6 +642,7 @@ class DefaultResidentCompiler implements ResidentCompiler {
required ShutdownHooks shutdownHooks,
this.testCompilation = false,
this.trackWidgetCreation = true,
this.includeUnsupportedPlatformLibraryStubs = false,
this.packagesPath,
List<String> fileSystemRoots = const <String>[],
this.fileSystemScheme,
@ -653,7 +665,17 @@ class DefaultResidentCompiler implements ResidentCompiler {
// This is a URI, not a file path, so the forward slash is correct even on Windows.
sdkRoot = sdkRoot.endsWith('/') ? sdkRoot : '$sdkRoot/',
// Make a copy, we might need to modify it later.
fileSystemRoots = List<String>.from(fileSystemRoots);
fileSystemRoots = List<String>.from(fileSystemRoots) {
// Currently, the only developer tooling that requires support for importing unsupported
// platform libraries at compile time is the widget previewer. Only developer tooling use cases
// should support this, so this is restricted to the widget previewer's runtime target for now.
if (includeUnsupportedPlatformLibraryStubs && targetModel != TargetModel.dartdevc) {
throw StateError(
'includeUnsupportedPlatformLibraryStubs should only be used by the widget-preview '
'command.',
);
}
}
final Logger _logger;
final ProcessManager _processManager;
@ -664,6 +686,7 @@ class DefaultResidentCompiler implements ResidentCompiler {
final bool testCompilation;
final BuildMode buildMode;
final bool trackWidgetCreation;
final bool includeUnsupportedPlatformLibraryStubs;
final String? packagesPath;
final TargetModel targetModel;
final List<String> fileSystemRoots;
@ -867,6 +890,8 @@ class DefaultResidentCompiler implements ResidentCompiler {
if (packagesPath != null) ...<String>['--packages', packagesPath!],
...buildModeOptions(buildMode, dartDefines),
if (trackWidgetCreation) '--track-widget-creation',
if (includeUnsupportedPlatformLibraryStubs)
'--include-unsupported-platform-library-stubs',
for (final String root in fileSystemRoots) ...<String>['--filesystem-root', root],
if (fileSystemScheme != null) ...<String>['--filesystem-scheme', fileSystemScheme!],
if (initializeFromDill != null) ...<String>[
@ -886,7 +911,7 @@ class DefaultResidentCompiler implements ResidentCompiler {
if (unsafePackageSerialization) '--unsafe-package-serialization',
// See: https://github.com/flutter/flutter/issues/103994
'--verbosity=error',
...?extraFrontEndOptions,
...?_filterExtraFrontEndOptions(extraFrontEndOptions),
];
_logger.printTrace(command.join(' '));
_server = await _processManager.start(command);

View File

@ -125,6 +125,7 @@ class FlutterDevice {
globals.artifacts!.getHostArtifact(HostArtifact.flutterWebSdk).path,
buildMode: buildInfo.mode,
trackWidgetCreation: buildInfo.trackWidgetCreation,
includeUnsupportedPlatformLibraryStubs: buildInfo.includeUnsupportedPlatformLibraryStubs,
fileSystemRoots: buildInfo.fileSystemRoots,
// Override the filesystem scheme so that the frontend_server can find
// the generated entrypoint code.

View File

@ -2,13 +2,21 @@
// 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:file/file.dart';
import 'package:file/memory.dart';
import 'package:flutter_tools/src/artifacts.dart';
import 'package:flutter_tools/src/base/common.dart';
import 'package:flutter_tools/src/base/logger.dart';
import 'package:flutter_tools/src/base/platform.dart';
import 'package:flutter_tools/src/build_info.dart';
import 'package:flutter_tools/src/compile.dart';
import 'package:package_config/package_config.dart';
import '../src/common.dart';
import '../src/fake_process_manager.dart';
import '../src/fakes.dart' hide FakeProcess;
void main() {
testWithoutContext('StdoutHandler can produce output message', () async {
@ -147,4 +155,195 @@ void main() {
);
},
);
testWithoutContext(
'includeUnsupportedPlatformLibraryStubs is only valid for Target.dartdevc',
() {
final unsupportedTargetModels = <TargetModel>{
TargetModel.flutter,
TargetModel.flutterRunner,
TargetModel.vm,
};
// Initializing the compiler with includeUnsupportedPlatformLibraryStubs for targets other
// than DDC is not currently supported as it's limited for use with the widget previewer.
for (final target in unsupportedTargetModels) {
try {
ResidentCompiler(
'sdkroot',
buildMode: BuildMode.debug,
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
artifacts: Artifacts.test(),
platform: FakePlatform(),
fileSystem: MemoryFileSystem.test(),
shutdownHooks: FakeShutdownHooks(),
targetModel: target,
includeUnsupportedPlatformLibraryStubs: true,
);
fail('Unsupported target did not throw.');
} on StateError catch (e) {
expect(
e.message,
'includeUnsupportedPlatformLibraryStubs should only be used by the widget-preview '
'command.',
);
}
}
// Initializing the compiler with includeUnsupportedPlatformLibraryStubs for DDC is
// supported.
ResidentCompiler(
'sdkroot',
buildMode: BuildMode.debug,
logger: BufferLogger.test(),
processManager: FakeProcessManager.any(),
artifacts: Artifacts.test(),
platform: FakePlatform(),
fileSystem: MemoryFileSystem.test(),
shutdownHooks: FakeShutdownHooks(),
targetModel: TargetModel.dartdevc,
includeUnsupportedPlatformLibraryStubs: true,
);
},
);
testWithoutContext(
'Strips --include-unsupported-platform-library-stubs from extraFrontEndOptions',
() async {
final completer = Completer<void>();
final processManager = FakeProcessManager.list([
FakeCommand(
command: const <String>[
'Artifact.engineDartAotRuntime.TargetPlatform.web_javascript',
'Artifact.frontendServerSnapshotForEngineDartSdk.TargetPlatform.web_javascript',
'--sdk-root',
'sdkroot/',
'--incremental',
'--target=dartdevc',
'--experimental-emit-debug-metadata',
'--output-dill',
'foo.dill',
'-Ddart.vm.profile=false',
'-Ddart.vm.product=false',
'--enable-asserts',
'--track-widget-creation',
'--verbosity=error',
'--extra-flag',
],
onRun: (_) => completer.complete(),
),
]);
final compiler = DefaultResidentCompiler(
'sdkroot',
buildMode: BuildMode.debug,
logger: BufferLogger.test(),
processManager: processManager,
artifacts: Artifacts.test(),
platform: FakePlatform(),
fileSystem: MemoryFileSystem.test(),
shutdownHooks: FakeShutdownHooks(),
targetModel: TargetModel.dartdevc,
// Don't explicitly enable includeUnsupportedPlatformLibraryStubs to ensure it's not
// included in the argument list.
// ignore: avoid_redundant_argument_values
includeUnsupportedPlatformLibraryStubs: false,
extraFrontEndOptions: [
'--include-unsupported-platform-library-stubs',
// Include a random extra flag to ensure not all extra options are stripped.
'--extra-flag',
],
);
await runZonedGuarded(
() {
// This throws ToolExit as the FakeProcess immediately closes stdout and stderr.
compiler.recompile(
Uri.file('foo.dart'),
[],
outputPath: 'foo.dill',
packageConfig: PackageConfig.empty,
);
},
(e, st) {
if (e is! ToolExit) {
completer.completeError(e, st);
}
},
);
// Fail if the command isn't run. This can happen when the commands actual arguments don't
// match.
await completer.future.timeout(const Duration(seconds: 5));
},
);
testWithoutContext(
'--include-unsupported-platform-library-stubs when includeUnsupportedPlatformLibraryStubs is set',
() async {
final completer = Completer<void>();
final processManager = FakeProcessManager.list([
FakeCommand(
command: const <String>[
'Artifact.engineDartAotRuntime.TargetPlatform.web_javascript',
'Artifact.frontendServerSnapshotForEngineDartSdk.TargetPlatform.web_javascript',
'--sdk-root',
'sdkroot/',
'--incremental',
'--target=dartdevc',
'--experimental-emit-debug-metadata',
'--output-dill',
'foo.dill',
'-Ddart.vm.profile=false',
'-Ddart.vm.product=false',
'--enable-asserts',
'--track-widget-creation',
'--include-unsupported-platform-library-stubs',
'--verbosity=error',
'--extra-flag',
],
onRun: (_) => completer.complete(),
),
]);
final compiler = DefaultResidentCompiler(
'sdkroot',
buildMode: BuildMode.debug,
logger: BufferLogger.test(),
processManager: processManager,
artifacts: Artifacts.test(),
platform: FakePlatform(),
fileSystem: MemoryFileSystem.test(),
shutdownHooks: FakeShutdownHooks(),
targetModel: TargetModel.dartdevc,
// Explicitly enable includeUnsupportedPlatformLibraryStubs to ensure it's included in the
// argument list.
includeUnsupportedPlatformLibraryStubs: true,
extraFrontEndOptions: [
// Include a random extra flag to ensure not all extra options are stripped.
'--extra-flag',
],
);
await runZonedGuarded(
() {
// This throws ToolExit as the FakeProcess immediately closes stdout and stderr.
compiler.recompile(
Uri.file('foo.dart'),
[],
outputPath: 'foo.dill',
packageConfig: PackageConfig.empty,
);
},
(e, st) {
if (e is! ToolExit) {
completer.completeError(e, st);
}
},
);
// Fail if the command isn't run. This can happen when the commands actual arguments don't
// match.
await completer.future.timeout(const Duration(seconds: 5));
},
);
}