mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
285 lines
10 KiB
Dart
285 lines
10 KiB
Dart
// 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.
|
|
|
|
// Note: this Builder does not run in the same process as the flutter_tool, so
|
|
// the DI provided getters such as `fs` will not work.
|
|
import 'dart:async';
|
|
import 'dart:convert';
|
|
import 'dart:io';
|
|
import 'package:build/build.dart';
|
|
import 'package:package_config/packages_file.dart' as packages_file;
|
|
import 'package:meta/meta.dart';
|
|
import 'package:path/path.dart' as path;
|
|
|
|
const String _kFlutterDillOutputExtension = '.app.dill';
|
|
const String _kPackagesExtension = '.packages';
|
|
const String _kMultirootScheme = 'org-dartlang-app';
|
|
|
|
/// A builder which creates a kernel and packages file for a Flutter app.
|
|
///
|
|
/// Unlike the package:build kernel builders, this creates a single kernel from
|
|
/// dart source using the frontend server binary. The newly created .package
|
|
/// file replaces the relative root of the current package with a multi-root
|
|
/// which includes the generated directory.
|
|
class FlutterKernelBuilder implements Builder {
|
|
const FlutterKernelBuilder({
|
|
@required this.disabled,
|
|
@required this.mainPath,
|
|
@required this.aot,
|
|
@required this.trackWidgetCreation,
|
|
@required this.targetProductVm,
|
|
@required this.linkPlatformKernelIn,
|
|
@required this.extraFrontEndOptions,
|
|
@required this.sdkRoot,
|
|
@required this.packagesPath,
|
|
@required this.incrementalCompilerByteStorePath,
|
|
@required this.frontendServerPath,
|
|
@required this.engineDartBinaryPath,
|
|
});
|
|
|
|
/// The path to the entrypoint that will be compiled.
|
|
final String mainPath;
|
|
|
|
/// The path to the pub generated .packages file.
|
|
final String packagesPath;
|
|
|
|
/// The path to the root of the flutter patched SDK.
|
|
final String sdkRoot;
|
|
|
|
/// The path to the frontend server snapshot.
|
|
final String frontendServerPath;
|
|
|
|
/// The path to the dart executable to use to run the frontend server
|
|
/// snapshot.
|
|
final String engineDartBinaryPath;
|
|
|
|
/// Whether to build an ahead of time build.
|
|
final bool aot;
|
|
|
|
/// Whether to disable production of kernel.
|
|
final bool disabled;
|
|
|
|
/// Whether the `trackWidgetCreation` flag is provided to the frontend
|
|
/// server.
|
|
final bool trackWidgetCreation;
|
|
|
|
/// Whether to provide the Dart product define to the frontend server.
|
|
final bool targetProductVm;
|
|
|
|
/// When in batch mode, link platform kernel file into result kernel file.
|
|
final bool linkPlatformKernelIn;
|
|
|
|
/// Whether to compile incrementally.
|
|
final String incrementalCompilerByteStorePath;
|
|
|
|
/// Additional arguments to pass to the frontend server.
|
|
final List<String> extraFrontEndOptions;
|
|
|
|
@override
|
|
Map<String, List<String>> get buildExtensions => const <String, List<String>>{
|
|
'.dart': <String>[_kFlutterDillOutputExtension, _kPackagesExtension],
|
|
};
|
|
|
|
@override
|
|
Future<void> build(BuildStep buildStep) async {
|
|
// Do not resolve dependencies if this does not correspond to the main
|
|
// entrypoint. Do not generate kernel if it has been disabled.
|
|
if (!mainPath.contains(buildStep.inputId.path) || disabled) {
|
|
return;
|
|
}
|
|
final AssetId outputId = buildStep.inputId.changeExtension(_kFlutterDillOutputExtension);
|
|
final AssetId packagesOutputId = buildStep.inputId.changeExtension(_kPackagesExtension);
|
|
|
|
// Create a scratch space file that can be read/written by the frontend server.
|
|
// It is okay to hard-code these file names because we will copy them back
|
|
// from the temp directory at the end of the build step.
|
|
final Directory tempDirecory = await Directory.systemTemp.createTemp('_flutter_build');
|
|
final File packagesFile = File(path.join(tempDirecory.path, _kPackagesExtension));
|
|
final File outputFile = File(path.join(tempDirecory.path, 'main.app.dill'));
|
|
await outputFile.create();
|
|
await packagesFile.create();
|
|
|
|
final Directory projectDir = File(packagesPath).parent;
|
|
final String packageName = buildStep.inputId.package;
|
|
final String oldPackagesContents = await File(packagesPath).readAsString();
|
|
// Note: currently we only replace the root package with a multiroot
|
|
// scheme. To support codegen on arbitrary packages we will need to do
|
|
// this for each dependency.
|
|
final String newPackagesContents = oldPackagesContents.replaceFirst('$packageName:lib/', '$packageName:$_kMultirootScheme:/');
|
|
await packagesFile.writeAsString(newPackagesContents);
|
|
String absoluteMainPath;
|
|
if (path.isAbsolute(mainPath)) {
|
|
absoluteMainPath = mainPath;
|
|
} else {
|
|
absoluteMainPath = path.join(projectDir.absolute.path, mainPath);
|
|
}
|
|
|
|
// start up the frontend server with configuration.
|
|
final List<String> arguments = <String>[
|
|
frontendServerPath,
|
|
'--sdk-root',
|
|
sdkRoot,
|
|
'--strong',
|
|
'--target=flutter',
|
|
];
|
|
if (trackWidgetCreation) {
|
|
arguments.add('--track-widget-creation');
|
|
}
|
|
if (!linkPlatformKernelIn) {
|
|
arguments.add('--no-link-platform');
|
|
}
|
|
if (aot) {
|
|
arguments.add('--aot');
|
|
arguments.add('--tfa');
|
|
}
|
|
if (targetProductVm) {
|
|
arguments.add('-Ddart.vm.product=true');
|
|
}
|
|
if (incrementalCompilerByteStorePath != null) {
|
|
arguments.add('--incremental');
|
|
}
|
|
final String generatedRoot = path.join(projectDir.absolute.path, '.dart_tool', 'build', 'generated', '$packageName', 'lib${Platform.pathSeparator}');
|
|
final String normalRoot = path.join(projectDir.absolute.path, 'lib${Platform.pathSeparator}');
|
|
arguments.addAll(<String>[
|
|
'--packages',
|
|
Uri.file(packagesFile.path).toString(),
|
|
'--output-dill',
|
|
outputFile.path,
|
|
'--filesystem-root',
|
|
normalRoot,
|
|
'--filesystem-root',
|
|
generatedRoot,
|
|
'--filesystem-scheme',
|
|
_kMultirootScheme,
|
|
]);
|
|
if (extraFrontEndOptions != null) {
|
|
arguments.addAll(extraFrontEndOptions);
|
|
}
|
|
final Uri mainUri = _PackageUriMapper.findUri(
|
|
absoluteMainPath,
|
|
packagesFile.path,
|
|
_kMultirootScheme,
|
|
<String>[normalRoot, generatedRoot],
|
|
);
|
|
arguments.add(mainUri?.toString() ?? absoluteMainPath);
|
|
// Invoke the frontend server and copy the dill back to the output
|
|
// directory.
|
|
try {
|
|
final Process server = await Process.start(engineDartBinaryPath, arguments);
|
|
final _StdoutHandler _stdoutHandler = _StdoutHandler();
|
|
server.stderr
|
|
.transform<String>(utf8.decoder)
|
|
.listen(log.shout);
|
|
server.stdout
|
|
.transform<String>(utf8.decoder)
|
|
.transform<String>(const LineSplitter())
|
|
.listen(_stdoutHandler.handler);
|
|
await server.exitCode;
|
|
await _stdoutHandler.compilerOutput.future;
|
|
await buildStep.writeAsBytes(outputId, await outputFile.readAsBytes());
|
|
await buildStep.writeAsBytes(packagesOutputId, await packagesFile.readAsBytes());
|
|
} catch (err, stackTrace) {
|
|
log.shout('frontend server failed to start: $err, $stackTrace');
|
|
}
|
|
}
|
|
}
|
|
|
|
class _StdoutHandler {
|
|
_StdoutHandler() {
|
|
reset();
|
|
}
|
|
|
|
bool compilerMessageReceived = false;
|
|
String boundaryKey;
|
|
Completer<_CompilerOutput> compilerOutput;
|
|
|
|
bool _suppressCompilerMessages;
|
|
|
|
void handler(String message) {
|
|
const String kResultPrefix = 'result ';
|
|
if (boundaryKey == null) {
|
|
if (message.startsWith(kResultPrefix))
|
|
boundaryKey = message.substring(kResultPrefix.length);
|
|
} else if (message.startsWith(boundaryKey)) {
|
|
if (message.length <= boundaryKey.length) {
|
|
compilerOutput.complete(null);
|
|
return;
|
|
}
|
|
final int spaceDelimiter = message.lastIndexOf(' ');
|
|
compilerOutput.complete(
|
|
_CompilerOutput(
|
|
message.substring(boundaryKey.length + 1, spaceDelimiter),
|
|
int.parse(message.substring(spaceDelimiter + 1).trim())));
|
|
} else if (!_suppressCompilerMessages) {
|
|
if (compilerMessageReceived == false) {
|
|
log.info('\nCompiler message:');
|
|
compilerMessageReceived = true;
|
|
}
|
|
log.info(message);
|
|
}
|
|
}
|
|
|
|
// This is needed to get ready to process next compilation result output,
|
|
// with its own boundary key and new completer.
|
|
void reset({bool suppressCompilerMessages = false}) {
|
|
boundaryKey = null;
|
|
compilerMessageReceived = false;
|
|
compilerOutput = Completer<_CompilerOutput>();
|
|
_suppressCompilerMessages = suppressCompilerMessages;
|
|
}
|
|
}
|
|
|
|
|
|
class _CompilerOutput {
|
|
const _CompilerOutput(this.outputFilename, this.errorCount);
|
|
|
|
final String outputFilename;
|
|
final int errorCount;
|
|
}
|
|
|
|
/// Converts filesystem paths to package URIs.
|
|
class _PackageUriMapper {
|
|
_PackageUriMapper(String scriptPath, String packagesPath, String fileSystemScheme, List<String> fileSystemRoots) {
|
|
final List<int> bytes = File(path.absolute(packagesPath)).readAsBytesSync();
|
|
final Map<String, Uri> packageMap = packages_file.parse(bytes, Uri.file(packagesPath, windows: Platform.isWindows));
|
|
final String scriptUri = Uri.file(scriptPath, windows: Platform.isWindows).toString();
|
|
|
|
for (String packageName in packageMap.keys) {
|
|
final String prefix = packageMap[packageName].toString();
|
|
if (fileSystemScheme != null && fileSystemRoots != null && prefix.contains(fileSystemScheme)) {
|
|
_packageName = packageName;
|
|
_uriPrefixes = fileSystemRoots
|
|
.map((String name) => Uri.file(name, windows: Platform.isWindows).toString())
|
|
.toList();
|
|
return;
|
|
}
|
|
if (scriptUri.startsWith(prefix)) {
|
|
_packageName = packageName;
|
|
_uriPrefixes = <String>[prefix];
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
String _packageName;
|
|
List<String> _uriPrefixes;
|
|
|
|
Uri map(String scriptPath) {
|
|
if (_packageName == null) {
|
|
return null;
|
|
}
|
|
final String scriptUri = Uri.file(scriptPath, windows: Platform.isWindows).toString();
|
|
for (String uriPrefix in _uriPrefixes) {
|
|
if (scriptUri.startsWith(uriPrefix)) {
|
|
return Uri.parse('package:$_packageName/${scriptUri.substring(uriPrefix.length)}');
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
static Uri findUri(String scriptPath, String packagesPath, String fileSystemScheme, List<String> fileSystemRoots) {
|
|
return _PackageUriMapper(scriptPath, packagesPath, fileSystemScheme, fileSystemRoots).map(scriptPath);
|
|
}
|
|
}
|