blob: 731957978be6a5ed45e32777a56df3834c8903ea [file] [log] [blame]
// Copyright 2019 The Chromium Authors. All rights reserved.
// 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:build_daemon/data/build_status.dart';
import 'package:build_daemon/data/build_target.dart';
import 'package:build_runner_core/build_runner_core.dart' hide BuildStatus;
import 'package:build_daemon/data/server_log.dart';
import 'package:build_daemon/data/build_status.dart' as build;
import 'package:build_daemon/client.dart';
import 'package:meta/meta.dart';
import 'package:yaml/yaml.dart';
import '../artifacts.dart';
import '../base/file_system.dart';
import '../base/io.dart';
import '../base/logger.dart';
import '../base/process_manager.dart';
import '../cache.dart';
import '../codegen.dart';
import '../convert.dart';
import '../dart/pub.dart';
import '../globals.dart';
import '../project.dart';
import '../resident_runner.dart';
import 'build_script_generator.dart';
/// A wrapper for a build_runner process which delegates to a generated
/// build script.
///
/// This is only enabled if [experimentalBuildEnabled] is true, and only for
/// external flutter users.
class BuildRunner extends CodeGenerator {
const BuildRunner();
@override
Future<CodeGenerationResult> build({
@required String mainPath,
@required bool aot,
@required bool linkPlatformKernelIn,
@required bool trackWidgetCreation,
@required bool targetProductVm,
List<String> extraFrontEndOptions = const <String>[],
bool disableKernelGeneration = false,
}) async {
await generateBuildScript();
final FlutterProject flutterProject = await FlutterProject.current();
final String frontendServerPath = artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk
);
final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath);
final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
final String packagesPath = flutterProject.packagesFile.absolute.path;
final String buildScript = flutterProject
.dartTool
.childDirectory('build')
.childDirectory('entrypoint')
.childFile('build.dart')
.path;
final String scriptPackagesPath = flutterProject
.dartTool
.childDirectory('flutter_tool')
.childFile('.packages')
.path;
final String dartPath = fs.path.join(Cache.flutterRoot, 'bin', 'cache', 'dart-sdk', 'bin', 'dart');
final Status status = logger.startProgress('running builders...', timeout: null);
try {
final Process buildProcess = await processManager.start(<String>[
dartPath,
'--packages=$scriptPackagesPath',
buildScript,
'build',
'--skip-build-script-check',
'--define', 'flutter_build|kernel=disabled=$disableKernelGeneration',
'--define', 'flutter_build|kernel=aot=$aot',
'--define', 'flutter_build|kernel=linkPlatformKernelIn=$linkPlatformKernelIn',
'--define', 'flutter_build|kernel=trackWidgetCreation=$trackWidgetCreation',
'--define', 'flutter_build|kernel=targetProductVm=$targetProductVm',
'--define', 'flutter_build|kernel=mainPath=$mainPath',
'--define', 'flutter_build|kernel=packagesPath=$packagesPath',
'--define', 'flutter_build|kernel=sdkRoot=$sdkRoot',
'--define', 'flutter_build|kernel=frontendServerPath=$frontendServerPath',
'--define', 'flutter_build|kernel=engineDartBinaryPath=$engineDartBinaryPath',
'--define', 'flutter_build|kernel=extraFrontEndOptions=${extraFrontEndOptions ?? const <String>[]}',
]);
buildProcess
.stdout
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printTrace);
buildProcess
.stderr
.transform(utf8.decoder)
.transform(const LineSplitter())
.listen(printError);
await buildProcess.exitCode;
} finally {
status.stop();
}
if (disableKernelGeneration) {
return const CodeGenerationResult(null, null);
}
/// We don't check for this above because it might be generated for the
/// first time by invoking the build.
final Directory dartTool = flutterProject.dartTool;
final String projectName = flutterProject.manifest.appName;
final Directory generatedDirectory = dartTool
.absolute
.childDirectory('build')
.childDirectory('generated')
.childDirectory(projectName);
if (!generatedDirectory.existsSync()) {
throw Exception('build_runner cannot find generated directory');
}
final String relativeMain = fs.path.relative(mainPath, from: flutterProject.directory.path);
final File packagesFile = fs.file(
fs.path.join(generatedDirectory.path, fs.path.setExtension(relativeMain, '.packages'))
);
final File dillFile = fs.file(
fs.path.join(generatedDirectory.path, fs.path.setExtension(relativeMain, '.app.dill'))
);
if (!packagesFile.existsSync() || !dillFile.existsSync()) {
throw Exception('build_runner did not produce output at expected location: ${dillFile.path} missing');
}
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
Future<void> generateBuildScript() async {
final FlutterProject flutterProject = await FlutterProject.current();
final String generatedDirectory = fs.path.join(flutterProject.dartTool.path, 'flutter_tool');
final String resultScriptPath = fs.path.join(flutterProject.dartTool.path, 'build', 'entrypoint', 'build.dart');
if (fs.file(resultScriptPath).existsSync()) {
return;
}
final Status status = logger.startProgress('generating build script...', timeout: null);
try {
fs.directory(generatedDirectory).createSync(recursive: true);
final File syntheticPubspec = fs.file(fs.path.join(generatedDirectory, 'pubspec.yaml'));
final StringBuffer stringBuffer = StringBuffer();
stringBuffer.writeln('name: flutter_tool');
stringBuffer.writeln('dependencies:');
final YamlMap builders = await flutterProject.builders;
if (builders != null) {
for (String name in builders.keys) {
final YamlNode node = builders[name];
stringBuffer.writeln(' $name: $node');
}
}
stringBuffer.writeln(' build_runner: any');
stringBuffer.writeln(' flutter_build:');
stringBuffer.writeln(' sdk: flutter');
await syntheticPubspec.writeAsString(stringBuffer.toString());
await pubGet(
context: PubContext.pubGet,
directory: generatedDirectory,
upgrade: false,
checkLastModified: false,
);
final PackageGraph packageGraph = PackageGraph.forPath(syntheticPubspec.parent.path);
final BuildScriptGenerator buildScriptGenerator = const BuildScriptGeneratorFactory().create(flutterProject, packageGraph);
await buildScriptGenerator.generateBuildScript();
} finally {
status.stop();
}
}
@override
Future<CodegenDaemon> daemon({
String mainPath,
bool linkPlatformKernelIn = false,
bool targetProductVm = false,
bool trackWidgetCreation = false,
List<String> extraFrontEndOptions = const <String> [],
}) async {
mainPath ??= findMainDartFile();
await generateBuildScript();
final FlutterProject flutterProject = await FlutterProject.current();
final String frontendServerPath = artifacts.getArtifactPath(
Artifact.frontendServerSnapshotForEngineDartSdk
);
final String sdkRoot = artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath);
final String engineDartBinaryPath = artifacts.getArtifactPath(Artifact.engineDartBinary);
final String packagesPath = flutterProject.packagesFile.absolute.path;
final String buildScript = flutterProject
.dartTool
.childDirectory('build')
.childDirectory('entrypoint')
.childFile('build.dart')
.path;
final String scriptPackagesPath = flutterProject
.dartTool
.childDirectory('flutter_tool')
.childFile('.packages')
.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);
BuildDaemonClient buildDaemonClient;
try {
final List<String> command = <String>[
dartPath,
'--packages=$scriptPackagesPath',
buildScript,
'daemon',
'--skip-build-script-check',
'--define', 'flutter_build|kernel=disabled=false',
'--define', 'flutter_build|kernel=aot=false',
'--define', 'flutter_build|kernel=linkPlatformKernelIn=$linkPlatformKernelIn',
'--define', 'flutter_build|kernel=trackWidgetCreation=$trackWidgetCreation',
'--define', 'flutter_build|kernel=targetProductVm=$targetProductVm',
'--define', 'flutter_build|kernel=mainPath=$mainPath',
'--define', 'flutter_build|kernel=packagesPath=$packagesPath',
'--define', 'flutter_build|kernel=sdkRoot=$sdkRoot',
'--define', 'flutter_build|kernel=frontendServerPath=$frontendServerPath',
'--define', 'flutter_build|kernel=engineDartBinaryPath=$engineDartBinaryPath',
'--define', 'flutter_build|kernel=extraFrontEndOptions=${extraFrontEndOptions ?? const <String>[]}',
];
buildDaemonClient = await BuildDaemonClient.connect(flutterProject.directory.path, command, logHandler: (ServerLog log) => printTrace(log.toString()));
} finally {
status.stop();
}
buildDaemonClient.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder builder) {
builder.target = flutterProject.manifest.appName;
}));
final String relativeMain = fs.path.relative(mainPath, from: flutterProject.directory.path);
final File generatedPackagesFile = fs.file(fs.path.join(flutterProject.generated.path, fs.path.setExtension(relativeMain, '.packages')));
final File generatedDillFile = fs.file(fs.path.join(flutterProject.generated.path, fs.path.setExtension(relativeMain, '.app.dill')));
return _BuildRunnerCodegenDaemon(buildDaemonClient, generatedPackagesFile, generatedDillFile);
}
}
class _BuildRunnerCodegenDaemon implements CodegenDaemon {
_BuildRunnerCodegenDaemon(this.buildDaemonClient, this.packagesFile, this.dillFile);
final BuildDaemonClient buildDaemonClient;
@override
final File packagesFile;
@override
final File dillFile;
@override
CodegenStatus get lastStatus => _lastStatus;
CodegenStatus _lastStatus;
@override
Stream<CodegenStatus> get buildResults => buildDaemonClient.buildResults.map((build.BuildResults results) {
if (results.results.first.status == BuildStatus.failed) {
return _lastStatus = CodegenStatus.Failed;
}
if (results.results.first.status == BuildStatus.started) {
return _lastStatus = CodegenStatus.Started;
}
if (results.results.first.status == BuildStatus.succeeded) {
return _lastStatus = CodegenStatus.Succeeded;
}
_lastStatus = null;
return null;
});
@override
void startBuild() {
buildDaemonClient.startBuild();
}
}