| // Copyright 2014 The Flutter 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/client.dart'; |
| import 'package:build_daemon/data/build_status.dart'; |
| import 'package:build_daemon/data/build_status.dart' as build; |
| import 'package:build_daemon/data/build_target.dart'; |
| import 'package:build_daemon/data/server_log.dart'; |
| import 'package:crypto/crypto.dart' show md5; |
| import 'package:yaml/yaml.dart'; |
| |
| import '../artifacts.dart'; |
| import '../base/common.dart'; |
| import '../base/file_system.dart'; |
| import '../base/io.dart'; |
| import '../base/logger.dart'; |
| import '../codegen.dart'; |
| import '../dart/pub.dart'; |
| import '../dart/sdk.dart'; |
| import '../globals.dart' as globals; |
| import '../project.dart'; |
| |
| /// The minimum version of build_runner we can support in the flutter tool. |
| const String kMinimumBuildRunnerVersion = '1.7.1'; |
| const String kSupportedBuildDaemonVersion = '2.1.0'; |
| |
| /// 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<void> generateBuildScript(FlutterProject flutterProject) async { |
| final Directory entrypointDirectory = globals.fs.directory(globals.fs.path.join(flutterProject.dartTool.path, 'build', 'entrypoint')); |
| final Directory generatedDirectory = globals.fs.directory(globals.fs.path.join(flutterProject.dartTool.path, 'flutter_tool')); |
| 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 regenerate. |
| final YamlMap builders = 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 = globals.logger.startProgress('generating build script...', timeout: null); |
| try { |
| generatedDirectory.createSync(recursive: true); |
| entrypointDirectory.createSync(recursive: true); |
| flutterProject.dartTool.childDirectory('build').childDirectory('generated').createSync(recursive: true); |
| final StringBuffer stringBuffer = StringBuffer(); |
| |
| stringBuffer.writeln('name: flutter_tool'); |
| stringBuffer.writeln('dependencies:'); |
| final YamlMap builders = flutterProject.builders; |
| if (builders != null) { |
| for (final String name in builders.keys.cast<String>()) { |
| final Object node = builders[name]; |
| // For relative paths, make sure it is accounted for |
| // parent directories. |
| if (node is YamlMap && node['path'] != null) { |
| final String path = node['path'] as String; |
| if (globals.fs.path.isRelative(path)) { |
| final String convertedPath = globals.fs.path.join('..', '..', path); |
| stringBuffer.writeln(' $name:'); |
| stringBuffer.writeln(' path: $convertedPath'); |
| } else { |
| stringBuffer.writeln(' $name: $node'); |
| } |
| } else { |
| stringBuffer.writeln(' $name: $node'); |
| } |
| } |
| } |
| stringBuffer.writeln(' build_runner: ^$kMinimumBuildRunnerVersion'); |
| stringBuffer.writeln(' build_daemon: $kSupportedBuildDaemonVersion'); |
| syntheticPubspec.writeAsStringSync(stringBuffer.toString()); |
| |
| await pub.get( |
| context: PubContext.pubGet, |
| directory: generatedDirectory.path, |
| upgrade: false, |
| checkLastModified: false, |
| ); |
| if (!scriptIdFile.existsSync()) { |
| scriptIdFile.createSync(recursive: true); |
| } |
| scriptIdFile.writeAsBytesSync(appliedBuilderDigest); |
| final ProcessResult generateResult = await globals.processManager.run(<String>[ |
| sdkBinaryName('pub'), 'run', 'build_runner', 'generate-build-script', |
| ], workingDirectory: syntheticPubspec.parent.path); |
| if (generateResult.exitCode != 0) { |
| throwToolExit('Error generating build_script snapshot: ${generateResult.stderr}'); |
| } |
| final File buildScript = globals.fs.file(generateResult.stdout.trim()); |
| final ProcessResult result = await globals.processManager.run(<String>[ |
| globals.artifacts.getArtifactPath(Artifact.engineDartBinary), |
| '--snapshot=${buildSnapshot.path}', |
| '--snapshot-kind=app-jit', |
| '--packages=${globals.fs.path.join(generatedDirectory.path, '.packages')}', |
| buildScript.path, |
| ]); |
| if (result.exitCode != 0) { |
| throwToolExit('Error generating build_script snapshot: ${result.stderr}'); |
| } |
| } finally { |
| status.stop(); |
| } |
| } |
| |
| @override |
| Future<CodegenDaemon> daemon( |
| FlutterProject flutterProject, { |
| String mainPath, |
| bool linkPlatformKernelIn = false, |
| bool trackWidgetCreation = false, |
| List<String> extraFrontEndOptions = const <String> [], |
| }) async { |
| await generateBuildScript(flutterProject); |
| final String engineDartBinaryPath = globals.artifacts.getArtifactPath(Artifact.engineDartBinary); |
| final File buildSnapshot = flutterProject |
| .dartTool |
| .childDirectory('build') |
| .childDirectory('entrypoint') |
| .childFile('build.dart.snapshot'); |
| final String scriptPackagesPath = flutterProject |
| .dartTool |
| .childDirectory('flutter_tool') |
| .childFile('.packages') |
| .path; |
| final Status status = globals.logger.startProgress('starting build daemon...', timeout: null); |
| BuildDaemonClient buildDaemonClient; |
| try { |
| final List<String> command = <String>[ |
| engineDartBinaryPath, |
| '--packages=$scriptPackagesPath', |
| buildSnapshot.path, |
| 'daemon', |
| '--skip-build-script-check', |
| '--delete-conflicting-outputs', |
| ]; |
| buildDaemonClient = await BuildDaemonClient.connect( |
| flutterProject.directory.path, |
| command, |
| logHandler: (ServerLog log) { |
| if (log.message != null) { |
| globals.printTrace(log.message); |
| } |
| }, |
| ); |
| } finally { |
| status.stop(); |
| } |
| // Empty string indicates we should build everything. |
| final OutputLocation outputLocation = OutputLocation((OutputLocationBuilder b) => b |
| ..output = '' |
| ..useSymlinks = false |
| ..hoist = false, |
| ); |
| buildDaemonClient.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder builder) { |
| builder.target = 'lib'; |
| builder.outputLocation = outputLocation.toBuilder(); |
| })); |
| buildDaemonClient.registerBuildTarget(DefaultBuildTarget((DefaultBuildTargetBuilder builder) { |
| builder.target = 'test'; |
| builder.outputLocation = outputLocation.toBuilder(); |
| })); |
| return _BuildRunnerCodegenDaemon(buildDaemonClient); |
| } |
| } |
| |
| class _BuildRunnerCodegenDaemon implements CodegenDaemon { |
| _BuildRunnerCodegenDaemon(this.buildDaemonClient); |
| |
| final BuildDaemonClient buildDaemonClient; |
| |
| @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(); |
| } |
| } |
| |
| // 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(globals.platform.version.codeUnits).bytes; |
| } |
| final List<String> orderedBuilderNames = builders.keys |
| .cast<String>() |
| .toList()..sort(); |
| final List<String> orderedBuilderValues = builders.values |
| .map((dynamic value) => value.toString()) |
| .toList()..sort(); |
| return md5.convert(<String>[ |
| ...orderedBuilderNames, |
| ...orderedBuilderValues, |
| globals.platform.version, |
| ].join('').codeUnits).bytes; |
| } |