Skip to content

Commit dd94499

Browse files
author
Jonah Williams
authored
Add build script invalidation and snapshotting logic (flutter#28866)
1 parent e5b1ed7 commit dd94499

File tree

2 files changed

+71
-47
lines changed

2 files changed

+71
-47
lines changed

packages/flutter_tools/lib/src/build_runner/build_runner.dart

Lines changed: 71 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,14 @@ import 'package:build_daemon/data/build_status.dart' as build;
1212
import 'package:build_daemon/client.dart';
1313
import 'package:meta/meta.dart';
1414
import 'package:yaml/yaml.dart';
15+
import 'package:crypto/crypto.dart' show md5;
1516

1617
import '../artifacts.dart';
18+
import '../base/common.dart';
1719
import '../base/file_system.dart';
1820
import '../base/io.dart';
1921
import '../base/logger.dart';
2022
import '../base/process_manager.dart';
21-
import '../cache.dart';
2223
import '../codegen.dart';
2324
import '../convert.dart';
2425
import '../dart/pub.dart';
@@ -53,24 +54,23 @@ class BuildRunner extends CodeGenerator {
5354
final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath);
5455
final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
5556
final String packagesPath = flutterProject.packagesFile.absolute.path;
56-
final String buildScript = flutterProject
57+
final String buildSnapshot = flutterProject
5758
.dartTool
5859
.childDirectory('build')
5960
.childDirectory('entrypoint')
60-
.childFile('build.dart')
61+
.childFile('build.dart.snapshot')
6162
.path;
6263
final String scriptPackagesPath = flutterProject
6364
.dartTool
6465
.childDirectory('flutter_tool')
6566
.childFile('.packages')
6667
.path;
67-
final String dartPath = fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
6868
final Status status = logger.startProgress('running builders...', timeout: null);
6969
try {
7070
final Process buildProcess = await processManager.start(<String>[
71-
dartPath,
71+
engineDartBinaryPath,
7272
'--packages=$scriptPackagesPath',
73-
buildScript,
73+
buildSnapshot,
7474
'build',
7575
'--skip-build-script-check',
7676
'--define', 'flutter_build|kernel=disabled=$disableKernelGeneration',
@@ -95,7 +95,6 @@ class BuildRunner extends CodeGenerator {
9595
.transform(utf8.decoder)
9696
.transform(const LineSplitter())
9797
.listen(printError);
98-
await buildProcess.exitCode;
9998
} finally {
10099
status.stop();
101100
}
@@ -127,57 +126,84 @@ class BuildRunner extends CodeGenerator {
127126
return CodeGenerationResult(packagesFile, dillFile);
128127
}
129128

130-
@override
131-
Future<void> invalidateBuildScript() async {
132-
final FlutterProject flutterProject = await FlutterProject.current();
133-
final File buildScript = flutterProject.dartTool
134-
.absolute
135-
.childDirectory('flutter_tool')
136-
.childFile('build.dart');
137-
if (!buildScript.existsSync()) {
138-
return;
139-
}
140-
await buildScript.delete();
141-
}
142-
143129
@override
144130
Future<void> generateBuildScript() async {
145131
final FlutterProject flutterProject = await FlutterProject.current();
146-
final String generatedDirectory = fs.path.join(flutterProject.dartTool.path, 'flutter_tool');
147-
final String resultScriptPath = fs.path.join(flutterProject.dartTool.path, 'build', 'entrypoint', 'build.dart');
148-
if (fs.file(resultScriptPath).existsSync()) {
149-
return;
132+
final Directory entrypointDirectory = fs.directory(fs.path.join(flutterProject.dartTool.path, 'build', 'entrypoint'));
133+
final Directory generatedDirectory = fs.directory(fs.path.join(flutterProject.dartTool.path, 'flutter_tool'));
134+
final File buildScript = entrypointDirectory.childFile('build.dart');
135+
final File buildSnapshot = entrypointDirectory.childFile('build.dart.snapshot');
136+
final File scriptIdFile = entrypointDirectory.childFile('id');
137+
final File syntheticPubspec = generatedDirectory.childFile('pubspec.yaml');
138+
139+
// Check if contents of builders changed. If so, invalidate build script
140+
// and regnerate.
141+
final YamlMap builders = await flutterProject.builders;
142+
final List<int> appliedBuilderDigest = _produceScriptId(builders);
143+
if (scriptIdFile.existsSync() && buildSnapshot.existsSync()) {
144+
final List<int> previousAppliedBuilderDigest = scriptIdFile.readAsBytesSync();
145+
bool digestsAreEqual = false;
146+
if (appliedBuilderDigest.length == previousAppliedBuilderDigest.length) {
147+
digestsAreEqual = true;
148+
for (int i = 0; i < appliedBuilderDigest.length; i++) {
149+
if (appliedBuilderDigest[i] != previousAppliedBuilderDigest[i]) {
150+
digestsAreEqual = false;
151+
break;
152+
}
153+
}
154+
}
155+
if (digestsAreEqual) {
156+
return;
157+
}
158+
}
159+
// Clean-up all existing artifacts.
160+
if (flutterProject.dartTool.existsSync()) {
161+
flutterProject.dartTool.deleteSync(recursive: true);
150162
}
151163
final Status status = logger.startProgress('generating build script...', timeout: null);
152164
try {
153-
fs.directory(generatedDirectory).createSync(recursive: true);
154-
155-
final File syntheticPubspec = fs.file(fs.path.join(generatedDirectory, 'pubspec.yaml'));
165+
generatedDirectory.createSync(recursive: true);
166+
entrypointDirectory.createSync(recursive: true);
167+
flutterProject.dartTool.childDirectory('build').childDirectory('generated').createSync(recursive: true);
156168
final StringBuffer stringBuffer = StringBuffer();
157169

158170
stringBuffer.writeln('name: flutter_tool');
159171
stringBuffer.writeln('dependencies:');
160172
final YamlMap builders = await flutterProject.builders;
161173
if (builders != null) {
162174
for (String name in builders.keys) {
163-
final YamlNode node = builders[name];
175+
final Object node = builders[name];
164176
stringBuffer.writeln(' $name: $node');
165177
}
166178
}
167179
stringBuffer.writeln(' build_runner: any');
168180
stringBuffer.writeln(' flutter_build:');
169181
stringBuffer.writeln(' sdk: flutter');
170-
await syntheticPubspec.writeAsString(stringBuffer.toString());
182+
syntheticPubspec.writeAsStringSync(stringBuffer.toString());
171183

172184
await pubGet(
173185
context: PubContext.pubGet,
174-
directory: generatedDirectory,
186+
directory: generatedDirectory.path,
175187
upgrade: false,
176188
checkLastModified: false,
177189
);
190+
if (!scriptIdFile.existsSync()) {
191+
scriptIdFile.createSync(recursive: true);
192+
}
193+
scriptIdFile.writeAsBytesSync(appliedBuilderDigest);
178194
final PackageGraph packageGraph = PackageGraph.forPath(syntheticPubspec.parent.path);
179195
final BuildScriptGenerator buildScriptGenerator = const BuildScriptGeneratorFactory().create(flutterProject, packageGraph);
180196
await buildScriptGenerator.generateBuildScript();
197+
final ProcessResult result = await processManager.run(<String>[
198+
artifacts.getArtifactPath(Artifact.engineDartBinary),
199+
'--snapshot=${buildSnapshot.path}',
200+
'--snapshot-kind=app-jit',
201+
'--packages=${fs.path.join(generatedDirectory.path, '.packages')}',
202+
buildScript.path,
203+
]);
204+
if (result.exitCode != 0) {
205+
throwToolExit('Error generating build_script snapshot: ${result.stderr}');
206+
}
181207
} finally {
182208
status.stop();
183209
}
@@ -200,25 +226,23 @@ class BuildRunner extends CodeGenerator {
200226
final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath);
201227
final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
202228
final String packagesPath = flutterProject.packagesFile.absolute.path;
203-
final String buildScript = flutterProject
229+
final File buildSnapshot = flutterProject
204230
.dartTool
205231
.childDirectory('build')
206232
.childDirectory('entrypoint')
207-
.childFile('build.dart')
208-
.path;
233+
.childFile('build.dart.snapshot');
209234
final String scriptPackagesPath = flutterProject
210235
.dartTool
211236
.childDirectory('flutter_tool')
212237
.childFile('.packages')
213238
.path;
214-
final String dartPath = fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
215239
final Status status = logger.startProgress('starting build daemon...', timeout: null);
216240
BuildDaemonClient buildDaemonClient;
217241
try {
218242
final List<String> command = <String>[
219-
dartPath,
243+
engineDartBinaryPath,
220244
'--packages=$scriptPackagesPath',
221-
buildScript,
245+
buildSnapshot.path,
222246
'daemon',
223247
'--skip-build-script-check',
224248
'--define', 'flutter_build|kernel=disabled=false',
@@ -279,3 +303,14 @@ class _BuildRunnerCodegenDaemon implements CodegenDaemon {
279303
buildDaemonClient.startBuild();
280304
}
281305
}
306+
307+
// Sorts the builders by name and produces a hashcode of the resulting iterable.
308+
List<int> _produceScriptId(YamlMap builders) {
309+
if (builders == null || builders.isEmpty) {
310+
return md5.convert(<int>[]).bytes;
311+
}
312+
final List<String> orderedBuilders = builders.keys
313+
.cast<String>()
314+
.toList()..sort();
315+
return md5.convert(orderedBuilders.join('').codeUnits).bytes;
316+
}

packages/flutter_tools/lib/src/codegen.dart

Lines changed: 0 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -81,12 +81,6 @@ abstract class CodeGenerator {
8181
List<String> extraFrontEndOptions = const <String>[],
8282
});
8383

84-
/// Invalidates a generated build script by deleting it.
85-
///
86-
/// Must be called any time a pubspec file update triggers a corresponding change
87-
/// in .packages.
88-
Future<void> invalidateBuildScript();
89-
9084
// Generates a synthetic package under .dart_tool/flutter_tool which is in turn
9185
// used to generate a build script.
9286
Future<void> generateBuildScript();
@@ -113,11 +107,6 @@ class UnsupportedCodeGenerator extends CodeGenerator {
113107
throw UnsupportedError('build_runner is not currently supported.');
114108
}
115109

116-
@override
117-
Future<void> invalidateBuildScript() {
118-
throw UnsupportedError('build_runner is not currently supported.');
119-
}
120-
121110
@override
122111
Future<CodegenDaemon> daemon({
123112
String mainPath,

0 commit comments

Comments
 (0)