Skip to content

Commit ba626dc

Browse files
Wasm/JS Dual Compile with the flutter tool (#141396)
This implements dual compile via the newly available flutter.js bootstrapping APIs for intelligent build fallback. * Users can now use the `FlutterLoader.load` API from flutter.js * Flutter tool injects build info into the `index.html` of the user so that the bootstrapper knows which build variants are available to bootstrap * The semantics of the `--wasm` flag for `flutter build web` have changed: - Instead of producing a separate `build/web_wasm` directory, the output goes to the `build/web` directory like a normal web build - Produces a dual build that contains two build variants: dart2wasm+skwasm and dart2js+CanvasKit. The dart2wasm+skwasm will only work on Chrome in a cross-origin isolated context, all other environments will fall back to dart2js+CanvasKit. - `--wasm` and `--web-renderer` are now mutually exclusive. Since there are multiple build variants with `--wasm`, the web renderer cannot be expressed via a single command-line flag. For now, we are hard coding what build variants are produced with the `--wasm` flag, but I plan on making this more customizable in the future. * Build targets now can optionally provide a "build key" which can uniquely identify any specific parameterization of that build target. This way, the build target can invalidate itself by changing its build key. This works a bit better than just stuffing everything into the environment defines because (a) it doesn't invalidate the entire build, just the targets which are affected and (b) settings for multiple build variants don't translate well to the flat map of environment defines.
1 parent c6f2cea commit ba626dc

28 files changed

+735
-395
lines changed

dev/benchmarks/macrobenchmarks/web/index.html

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,12 @@
66
<head>
77
<meta charset="UTF-8">
88
<title>Web Benchmarks</title>
9+
<script src="flutter.js"></script>
910
</head>
1011
<body>
11-
<script src="main.dart.js" type="application/javascript"></script>
12+
<script>
13+
{{flutter_build_config}}
14+
_flutter.loader.load();
15+
</script>
1216
</body>
1317
</html>

dev/devicelab/lib/tasks/web_benchmarks.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ Future<TaskResult> runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async {
3939
'--omit-type-checks',
4040
],
4141
'--dart-define=FLUTTER_WEB_ENABLE_PROFILING=true',
42-
'--web-renderer=${benchmarkOptions.webRenderer}',
42+
if (!benchmarkOptions.useWasm) '--web-renderer=${benchmarkOptions.webRenderer}',
4343
'--profile',
4444
'--no-web-resources-cdn',
4545
'-t',
@@ -125,7 +125,7 @@ Future<TaskResult> runWebBenchmark(WebBenchmarkOptions benchmarkOptions) async {
125125
return Response.internalServerError(body: '$error');
126126
}
127127
}).add(createBuildDirectoryHandler(
128-
path.join(macrobenchmarksDirectory, 'build', benchmarkOptions.useWasm ? 'web_wasm' : 'web'),
128+
path.join(macrobenchmarksDirectory, 'build', 'web'),
129129
));
130130

131131
server = await io.HttpServer.bind('localhost', benchmarkServerPort);

dev/integration_tests/web_e2e_tests/web/index.html

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,14 +5,17 @@
55
<html>
66
<head>
77
<title>Web Integration Tests</title>
8-
<script>
9-
// Use the local CanvasKit bundle instead of the CDN to reduce test flakiness.
10-
window.flutterConfiguration = {
11-
canvasKitBaseUrl: "/canvaskit/"
12-
};
13-
</script>
8+
<script src="flutter.js"></script>
149
</head>
1510
<body>
16-
<script src="main.dart.js"></script>
11+
<script>
12+
{{flutter_build_config}}
13+
_flutter.loader.load({
14+
config: {
15+
// Use the local CanvasKit bundle instead of the CDN to reduce test flakiness.
16+
canvasKitBaseUrl: "/canvaskit/",
17+
},
18+
});
19+
</script>
1720
</body>
1821
</html>

packages/flutter_tools/lib/src/build_info.dart

Lines changed: 2 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@ import 'base/os.dart';
1212
import 'base/utils.dart';
1313
import 'convert.dart';
1414
import 'globals.dart' as globals;
15-
import 'web/compile.dart';
1615

1716
/// Whether icon font subsetting is enabled by default.
1817
const bool kIconTreeShakerEnabledDefault = true;
@@ -36,7 +35,6 @@ class BuildInfo {
3635
List<String>? dartDefines,
3736
this.bundleSkSLPath,
3837
List<String>? dartExperiments,
39-
this.webRenderer = WebRendererMode.auto,
4038
required this.treeShakeIcons,
4139
this.performanceMeasurementFile,
4240
this.packagesPath = '.dart_tool/package_config.json', // TODO(zanderso): make this required and remove the default.
@@ -130,9 +128,6 @@ class BuildInfo {
130128
/// A list of Dart experiments.
131129
final List<String> dartExperiments;
132130

133-
/// When compiling to web, which web renderer mode we are using (html, canvaskit, auto)
134-
final WebRendererMode webRenderer;
135-
136131
/// The name of a file where flutter assemble will output performance
137132
/// information in a JSON format.
138133
///
@@ -798,10 +793,6 @@ HostPlatform getCurrentHostPlatform() {
798793
return HostPlatform.linux_x64;
799794
}
800795

801-
FileSystemEntity getWebPlatformBinariesDirectory(Artifacts artifacts, WebRendererMode webRenderer) {
802-
return artifacts.getHostArtifact(HostArtifact.webPlatformKernelFolder);
803-
}
804-
805796
/// Returns the top-level build output directory.
806797
String getBuildDirectory([Config? config, FileSystem? fileSystem]) {
807798
// TODO(johnmccutchan): Stop calling this function as part of setting
@@ -844,8 +835,8 @@ String getMacOSBuildDirectory() {
844835
}
845836

846837
/// Returns the web build output directory.
847-
String getWebBuildDirectory([bool isWasm = false]) {
848-
return globals.fs.path.join(getBuildDirectory(), isWasm ? 'web_wasm' : 'web');
838+
String getWebBuildDirectory() {
839+
return globals.fs.path.join(getBuildDirectory(), 'web');
849840
}
850841

851842
/// Returns the Linux build output directory.

packages/flutter_tools/lib/src/build_system/build_system.dart

Lines changed: 105 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,15 @@ abstract class Target {
136136
/// A list of zero or more depfiles, located directly under {BUILD_DIR}.
137137
List<String> get depfiles => const <String>[];
138138

139+
/// A string that differentiates different build variants from each other
140+
/// with regards to build flags or settings on the target. This string should
141+
/// represent each build variant as a different unique value. If this value
142+
/// changes between builds, the target will be invalidated and rebuilt.
143+
///
144+
/// By default, this returns null, which indicates there is only one build
145+
/// variant, and the target won't invalidate or rebuild due to this property.
146+
String? get buildKey => null;
147+
139148
/// Whether this target can be executed with the given [environment].
140149
///
141150
/// Returning `true` will cause [build] to be skipped. This is equivalent
@@ -156,6 +165,7 @@ abstract class Target {
156165
<Node>[
157166
for (final Target target in dependencies) target._toNode(environment),
158167
],
168+
buildKey,
159169
environment,
160170
inputsFiles.containsNewDepfile,
161171
);
@@ -181,9 +191,11 @@ abstract class Target {
181191
for (final File output in outputs) {
182192
outputPaths.add(output.path);
183193
}
194+
final String? key = buildKey;
184195
final Map<String, Object> result = <String, Object>{
185196
'inputs': inputPaths,
186197
'outputs': outputPaths,
198+
if (key != null) 'buildKey': key,
187199
};
188200
if (!stamp.existsSync()) {
189201
stamp.createSync();
@@ -218,6 +230,7 @@ abstract class Target {
218230
/// This requires constants from the [Environment] to resolve the paths of
219231
/// inputs and the output stamp.
220232
Map<String, Object> toJson(Environment environment) {
233+
final String? key = buildKey;
221234
return <String, Object>{
222235
'name': name,
223236
'dependencies': <String>[
@@ -229,6 +242,7 @@ abstract class Target {
229242
'outputs': <String>[
230243
for (final File file in resolveOutputs(environment).sources) file.path,
231244
],
245+
if (key != null) 'buildKey': key,
232246
'stamp': _findStampFile(environment).absolute.path,
233247
};
234248
}
@@ -980,50 +994,86 @@ void verifyOutputDirectories(List<File> outputs, Environment environment, Target
980994

981995
/// A node in the build graph.
982996
class Node {
983-
Node(
984-
this.target,
985-
this.inputs,
986-
this.outputs,
987-
this.dependencies,
997+
factory Node(
998+
Target target,
999+
List<File> inputs,
1000+
List<File> outputs,
1001+
List<Node> dependencies,
1002+
String? buildKey,
9881003
Environment environment,
989-
this.missingDepfile,
1004+
bool missingDepfile,
9901005
) {
9911006
final File stamp = target._findStampFile(environment);
1007+
Map<String, Object?>? stampValues;
9921008

9931009
// If the stamp file doesn't exist, we haven't run this step before and
9941010
// all inputs were added.
995-
if (!stamp.existsSync()) {
996-
// No stamp file, not safe to skip.
997-
_dirty = true;
998-
return;
999-
}
1000-
final String content = stamp.readAsStringSync();
1001-
// Something went wrong writing the stamp file.
1002-
if (content.isEmpty) {
1003-
stamp.deleteSync();
1004-
// Malformed stamp file, not safe to skip.
1005-
_dirty = true;
1006-
return;
1007-
}
1008-
Map<String, Object?>? values;
1009-
try {
1010-
values = castStringKeyedMap(json.decode(content));
1011-
} on FormatException {
1012-
// The json is malformed in some way.
1013-
_dirty = true;
1014-
return;
1011+
if (stamp.existsSync()) {
1012+
final String content = stamp.readAsStringSync();
1013+
if (content.isEmpty) {
1014+
stamp.deleteSync();
1015+
} else {
1016+
try {
1017+
stampValues = castStringKeyedMap(json.decode(content));
1018+
} on FormatException {
1019+
// The json is malformed in some way.
1020+
}
1021+
}
10151022
}
1016-
final Object? inputs = values?['inputs'];
1017-
final Object? outputs = values?['outputs'];
1018-
if (inputs is List<Object?> && outputs is List<Object?>) {
1019-
inputs.cast<String?>().whereType<String>().forEach(previousInputs.add);
1020-
outputs.cast<String?>().whereType<String>().forEach(previousOutputs.add);
1021-
} else {
1022-
// The json is malformed in some way.
1023-
_dirty = true;
1023+
if (stampValues != null) {
1024+
final String? previousBuildKey = stampValues['buildKey'] as String?;
1025+
final Object? stampInputs = stampValues['inputs'];
1026+
final Object? stampOutputs = stampValues['outputs'];
1027+
if (stampInputs is List<Object?> && stampOutputs is List<Object?>) {
1028+
final Set<String> previousInputs = stampInputs.whereType<String>().toSet();
1029+
final Set<String> previousOutputs = stampOutputs.whereType<String>().toSet();
1030+
return Node.withStamp(
1031+
target,
1032+
inputs,
1033+
previousInputs,
1034+
outputs,
1035+
previousOutputs,
1036+
dependencies,
1037+
buildKey,
1038+
previousBuildKey,
1039+
missingDepfile,
1040+
);
1041+
}
10241042
}
1043+
return Node.withNoStamp(
1044+
target,
1045+
inputs,
1046+
outputs,
1047+
dependencies,
1048+
buildKey,
1049+
missingDepfile,
1050+
);
10251051
}
10261052

1053+
Node.withNoStamp(
1054+
this.target,
1055+
this.inputs,
1056+
this.outputs,
1057+
this.dependencies,
1058+
this.buildKey,
1059+
this.missingDepfile,
1060+
) : previousInputs = <String>{},
1061+
previousOutputs = <String>{},
1062+
previousBuildKey = null,
1063+
_dirty = true;
1064+
1065+
Node.withStamp(
1066+
this.target,
1067+
this.inputs,
1068+
this.previousInputs,
1069+
this.outputs,
1070+
this.previousOutputs,
1071+
this.dependencies,
1072+
this.buildKey,
1073+
this.previousBuildKey,
1074+
this.missingDepfile,
1075+
) : _dirty = false;
1076+
10271077
/// The resolved input files.
10281078
///
10291079
/// These files may not yet exist if they are produced by previous steps.
@@ -1034,6 +1084,11 @@ class Node {
10341084
/// These files may not yet exist if the target hasn't run yet.
10351085
final List<File> outputs;
10361086

1087+
/// The current build key of the target
1088+
///
1089+
/// See `buildKey` in the `Target` class for more information.
1090+
final String? buildKey;
1091+
10371092
/// Whether this node is missing a depfile.
10381093
///
10391094
/// This requires an additional pass of source resolution after the target
@@ -1047,10 +1102,15 @@ class Node {
10471102
final List<Node> dependencies;
10481103

10491104
/// Output file paths from the previous invocation of this build node.
1050-
final Set<String> previousOutputs = <String>{};
1105+
final Set<String> previousOutputs;
10511106

10521107
/// Input file paths from the previous invocation of this build node.
1053-
final Set<String> previousInputs = <String>{};
1108+
final Set<String> previousInputs;
1109+
1110+
/// The buildKey from the previous invocation of this build node.
1111+
///
1112+
/// See `buildKey` in the `Target` class for more information.
1113+
final String? previousBuildKey;
10541114

10551115
/// One or more reasons why a task was invalidated.
10561116
///
@@ -1074,6 +1134,10 @@ class Node {
10741134
FileSystem fileSystem,
10751135
Logger logger,
10761136
) {
1137+
if (buildKey != previousBuildKey) {
1138+
_invalidate(InvalidatedReasonKind.buildKeyChanged);
1139+
_dirty = true;
1140+
}
10771141
final Set<String> currentOutputPaths = <String>{
10781142
for (final File file in outputs) file.path,
10791143
};
@@ -1173,7 +1237,8 @@ class InvalidatedReason {
11731237
InvalidatedReasonKind.inputChanged => 'The following inputs have updated contents: ${data.join(',')}',
11741238
InvalidatedReasonKind.outputChanged => 'The following outputs have updated contents: ${data.join(',')}',
11751239
InvalidatedReasonKind.outputMissing => 'The following outputs were missing: ${data.join(',')}',
1176-
InvalidatedReasonKind.outputSetChanged => 'The following outputs were removed from the output set: ${data.join(',')}'
1240+
InvalidatedReasonKind.outputSetChanged => 'The following outputs were removed from the output set: ${data.join(',')}',
1241+
InvalidatedReasonKind.buildKeyChanged => 'The target build key changed.',
11771242
};
11781243
}
11791244
}
@@ -1195,4 +1260,7 @@ enum InvalidatedReasonKind {
11951260

11961261
/// The set of expected output files changed.
11971262
outputSetChanged,
1263+
1264+
/// The build key changed
1265+
buildKeyChanged,
11981266
}

0 commit comments

Comments
 (0)