Unverified Commit dd944994 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add build script invalidation and snapshotting logic (#28866)

parent e5b1ed7a
...@@ -12,13 +12,14 @@ import 'package:build_daemon/data/build_status.dart' as build; ...@@ -12,13 +12,14 @@ import 'package:build_daemon/data/build_status.dart' as build;
import 'package:build_daemon/client.dart'; import 'package:build_daemon/client.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'package:yaml/yaml.dart'; import 'package:yaml/yaml.dart';
import 'package:crypto/crypto.dart' show md5;
import '../artifacts.dart'; import '../artifacts.dart';
import '../base/common.dart';
import '../base/file_system.dart'; import '../base/file_system.dart';
import '../base/io.dart'; import '../base/io.dart';
import '../base/logger.dart'; import '../base/logger.dart';
import '../base/process_manager.dart'; import '../base/process_manager.dart';
import '../cache.dart';
import '../codegen.dart'; import '../codegen.dart';
import '../convert.dart'; import '../convert.dart';
import '../dart/pub.dart'; import '../dart/pub.dart';
...@@ -53,24 +54,23 @@ class BuildRunner extends CodeGenerator { ...@@ -53,24 +54,23 @@ class BuildRunner extends CodeGenerator {
final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath); final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath);
final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary); final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
final String packagesPath = flutterProject.packagesFile.absolute.path; final String packagesPath = flutterProject.packagesFile.absolute.path;
final String buildScript = flutterProject final String buildSnapshot = flutterProject
.dartTool .dartTool
.childDirectory('build') .childDirectory('build')
.childDirectory('entrypoint') .childDirectory('entrypoint')
.childFile('build.dart') .childFile('build.dart.snapshot')
.path; .path;
final String scriptPackagesPath = flutterProject final String scriptPackagesPath = flutterProject
.dartTool .dartTool
.childDirectory('flutter_tool') .childDirectory('flutter_tool')
.childFile('.packages') .childFile('.packages')
.path; .path;
final String dartPath = fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
final Status status = logger.startProgress('running builders...', timeout: null); final Status status = logger.startProgress('running builders...', timeout: null);
try { try {
final Process buildProcess = await processManager.start(<String>[ final Process buildProcess = await processManager.start(<String>[
dartPath, engineDartBinaryPath,
'--packages=$scriptPackagesPath', '--packages=$scriptPackagesPath',
buildScript, buildSnapshot,
'build', 'build',
'--skip-build-script-check', '--skip-build-script-check',
'--define', 'flutter_build|kernel=disabled=$disableKernelGeneration', '--define', 'flutter_build|kernel=disabled=$disableKernelGeneration',
...@@ -95,7 +95,6 @@ class BuildRunner extends CodeGenerator { ...@@ -95,7 +95,6 @@ class BuildRunner extends CodeGenerator {
.transform(utf8.decoder) .transform(utf8.decoder)
.transform(const LineSplitter()) .transform(const LineSplitter())
.listen(printError); .listen(printError);
await buildProcess.exitCode;
} finally { } finally {
status.stop(); status.stop();
} }
...@@ -127,32 +126,45 @@ class BuildRunner extends CodeGenerator { ...@@ -127,32 +126,45 @@ class BuildRunner extends CodeGenerator {
return CodeGenerationResult(packagesFile, dillFile); return CodeGenerationResult(packagesFile, dillFile);
} }
@override
Future<void> invalidateBuildScript() async {
final FlutterProject flutterProject = await FlutterProject.current();
final File buildScript = flutterProject.dartTool
.absolute
.childDirectory('flutter_tool')
.childFile('build.dart');
if (!buildScript.existsSync()) {
return;
}
await buildScript.delete();
}
@override @override
Future<void> generateBuildScript() async { Future<void> generateBuildScript() async {
final FlutterProject flutterProject = await FlutterProject.current(); final FlutterProject flutterProject = await FlutterProject.current();
final String generatedDirectory = fs.path.join(flutterProject.dartTool.path, 'flutter_tool'); final Directory entrypointDirectory = fs.directory(fs.path.join(flutterProject.dartTool.path, 'build', 'entrypoint'));
final String resultScriptPath = fs.path.join(flutterProject.dartTool.path, 'build', 'entrypoint', 'build.dart'); final Directory generatedDirectory = fs.directory(fs.path.join(flutterProject.dartTool.path, 'flutter_tool'));
if (fs.file(resultScriptPath).existsSync()) { final File buildScript = entrypointDirectory.childFile('build.dart');
return; final File buildSnapshot = entrypointDirectory.childFile('build.dart.snapshot');
final File scriptIdFile = entrypointDirectory.childFile('id');
final File syntheticPubspec = generatedDirectory.childFile('pubspec.yaml');
// Check if contents of builders changed. If so, invalidate build script
// and regnerate.
final YamlMap builders = await flutterProject.builders;
final List<int> appliedBuilderDigest = _produceScriptId(builders);
if (scriptIdFile.existsSync() && buildSnapshot.existsSync()) {
final List<int> previousAppliedBuilderDigest = scriptIdFile.readAsBytesSync();
bool digestsAreEqual = false;
if (appliedBuilderDigest.length == previousAppliedBuilderDigest.length) {
digestsAreEqual = true;
for (int i = 0; i < appliedBuilderDigest.length; i++) {
if (appliedBuilderDigest[i] != previousAppliedBuilderDigest[i]) {
digestsAreEqual = false;
break;
}
}
}
if (digestsAreEqual) {
return;
}
}
// Clean-up all existing artifacts.
if (flutterProject.dartTool.existsSync()) {
flutterProject.dartTool.deleteSync(recursive: true);
} }
final Status status = logger.startProgress('generating build script...', timeout: null); final Status status = logger.startProgress('generating build script...', timeout: null);
try { try {
fs.directory(generatedDirectory).createSync(recursive: true); generatedDirectory.createSync(recursive: true);
entrypointDirectory.createSync(recursive: true);
final File syntheticPubspec = fs.file(fs.path.join(generatedDirectory, 'pubspec.yaml')); flutterProject.dartTool.childDirectory('build').childDirectory('generated').createSync(recursive: true);
final StringBuffer stringBuffer = StringBuffer(); final StringBuffer stringBuffer = StringBuffer();
stringBuffer.writeln('name: flutter_tool'); stringBuffer.writeln('name: flutter_tool');
...@@ -160,24 +172,38 @@ class BuildRunner extends CodeGenerator { ...@@ -160,24 +172,38 @@ class BuildRunner extends CodeGenerator {
final YamlMap builders = await flutterProject.builders; final YamlMap builders = await flutterProject.builders;
if (builders != null) { if (builders != null) {
for (String name in builders.keys) { for (String name in builders.keys) {
final YamlNode node = builders[name]; final Object node = builders[name];
stringBuffer.writeln(' $name: $node'); stringBuffer.writeln(' $name: $node');
} }
} }
stringBuffer.writeln(' build_runner: any'); stringBuffer.writeln(' build_runner: any');
stringBuffer.writeln(' flutter_build:'); stringBuffer.writeln(' flutter_build:');
stringBuffer.writeln(' sdk: flutter'); stringBuffer.writeln(' sdk: flutter');
await syntheticPubspec.writeAsString(stringBuffer.toString()); syntheticPubspec.writeAsStringSync(stringBuffer.toString());
await pubGet( await pubGet(
context: PubContext.pubGet, context: PubContext.pubGet,
directory: generatedDirectory, directory: generatedDirectory.path,
upgrade: false, upgrade: false,
checkLastModified: false, checkLastModified: false,
); );
if (!scriptIdFile.existsSync()) {
scriptIdFile.createSync(recursive: true);
}
scriptIdFile.writeAsBytesSync(appliedBuilderDigest);
final PackageGraph packageGraph = PackageGraph.forPath(syntheticPubspec.parent.path); final PackageGraph packageGraph = PackageGraph.forPath(syntheticPubspec.parent.path);
final BuildScriptGenerator buildScriptGenerator = const BuildScriptGeneratorFactory().create(flutterProject, packageGraph); final BuildScriptGenerator buildScriptGenerator = const BuildScriptGeneratorFactory().create(flutterProject, packageGraph);
await buildScriptGenerator.generateBuildScript(); await buildScriptGenerator.generateBuildScript();
final ProcessResult result = await processManager.run(<String>[
artifacts.getArtifactPath(Artifact.engineDartBinary),
'--snapshot=${buildSnapshot.path}',
'--snapshot-kind=app-jit',
'--packages=${fs.path.join(generatedDirectory.path, '.packages')}',
buildScript.path,
]);
if (result.exitCode != 0) {
throwToolExit('Error generating build_script snapshot: ${result.stderr}');
}
} finally { } finally {
status.stop(); status.stop();
} }
...@@ -200,25 +226,23 @@ class BuildRunner extends CodeGenerator { ...@@ -200,25 +226,23 @@ class BuildRunner extends CodeGenerator {
final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath); final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath);
final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary); final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
final String packagesPath = flutterProject.packagesFile.absolute.path; final String packagesPath = flutterProject.packagesFile.absolute.path;
final String buildScript = flutterProject final File buildSnapshot = flutterProject
.dartTool .dartTool
.childDirectory('build') .childDirectory('build')
.childDirectory('entrypoint') .childDirectory('entrypoint')
.childFile('build.dart') .childFile('build.dart.snapshot');
.path;
final String scriptPackagesPath = flutterProject final String scriptPackagesPath = flutterProject
.dartTool .dartTool
.childDirectory('flutter_tool') .childDirectory('flutter_tool')
.childFile('.packages') .childFile('.packages')
.path; .path;
final String dartPath = fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
final Status status = logger.startProgress('starting build daemon...', timeout: null); final Status status = logger.startProgress('starting build daemon...', timeout: null);
BuildDaemonClient buildDaemonClient; BuildDaemonClient buildDaemonClient;
try { try {
final List<String> command = <String>[ final List<String> command = <String>[
dartPath, engineDartBinaryPath,
'--packages=$scriptPackagesPath', '--packages=$scriptPackagesPath',
buildScript, buildSnapshot.path,
'daemon', 'daemon',
'--skip-build-script-check', '--skip-build-script-check',
'--define', 'flutter_build|kernel=disabled=false', '--define', 'flutter_build|kernel=disabled=false',
...@@ -279,3 +303,14 @@ class _BuildRunnerCodegenDaemon implements CodegenDaemon { ...@@ -279,3 +303,14 @@ class _BuildRunnerCodegenDaemon implements CodegenDaemon {
buildDaemonClient.startBuild(); buildDaemonClient.startBuild();
} }
} }
// Sorts the builders by name and produces a hashcode of the resulting iterable.
List<int> _produceScriptId(YamlMap builders) {
if (builders == null || builders.isEmpty) {
return md5.convert(<int>[]).bytes;
}
final List<String> orderedBuilders = builders.keys
.cast<String>()
.toList()..sort();
return md5.convert(orderedBuilders.join('').codeUnits).bytes;
}
...@@ -81,12 +81,6 @@ abstract class CodeGenerator { ...@@ -81,12 +81,6 @@ abstract class CodeGenerator {
List<String> extraFrontEndOptions = const <String>[], List<String> extraFrontEndOptions = const <String>[],
}); });
/// Invalidates a generated build script by deleting it.
///
/// Must be called any time a pubspec file update triggers a corresponding change
/// in .packages.
Future<void> invalidateBuildScript();
// Generates a synthetic package under .dart_tool/flutter_tool which is in turn // Generates a synthetic package under .dart_tool/flutter_tool which is in turn
// used to generate a build script. // used to generate a build script.
Future<void> generateBuildScript(); Future<void> generateBuildScript();
...@@ -113,11 +107,6 @@ class UnsupportedCodeGenerator extends CodeGenerator { ...@@ -113,11 +107,6 @@ class UnsupportedCodeGenerator extends CodeGenerator {
throw UnsupportedError('build_runner is not currently supported.'); throw UnsupportedError('build_runner is not currently supported.');
} }
@override
Future<void> invalidateBuildScript() {
throw UnsupportedError('build_runner is not currently supported.');
}
@override @override
Future<CodegenDaemon> daemon({ Future<CodegenDaemon> daemon({
String mainPath, String mainPath,
......
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