1// Copyright 2019 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5import 'package:meta/meta.dart'; 6 7import 'artifacts.dart'; 8import 'base/context.dart'; 9import 'base/file_system.dart'; 10import 'base/platform.dart'; 11import 'compile.dart'; 12import 'dart/package_map.dart'; 13import 'globals.dart'; 14import 'project.dart'; 15 16// Arbitrarily chosen multi-root file scheme. This is used to configure the 17// frontend_server to resolve a package uri to multiple filesystem directories. 18// In this case, the source directory and a generated directory. 19const String kMultiRootScheme = 'org-dartlang-app'; 20 21/// The [CodeGenerator] instance. 22/// 23/// If [experimentalBuildEnabled] is false, this will contain an unsupported 24/// implementation. 25CodeGenerator get codeGenerator => context.get<CodeGenerator>(); 26 27/// A wrapper for a build_runner process which delegates to a generated 28/// build script. 29/// 30/// This is only enabled if [experimentalBuildEnabled] is true, and only for 31/// external flutter users. 32abstract class CodeGenerator { 33 const CodeGenerator(); 34 35 /// Starts a persistent code generting daemon. 36 /// 37 /// The defines of the daemon command are the arguments required in the 38 /// flutter_build kernel builder. 39 Future<CodegenDaemon> daemon(FlutterProject flutterProject); 40 41 // Generates a synthetic package under .dart_tool/flutter_tool which is in turn 42 // used to generate a build script. 43 Future<void> generateBuildScript(FlutterProject flutterProject); 44 45 /// Create generated packages file which adds a multi-root scheme to the user's 46 /// project directory. Currently we only replace the root package with a multiroot 47 /// scheme. To support codegen on arbitrary packages we would need to do 48 /// this for each dependency. 49 void updatePackages(FlutterProject flutterProject) { 50 final String oldPackagesContents = fs.file(PackageMap.globalPackagesPath).readAsStringSync(); 51 final String appName = flutterProject.manifest.appName; 52 final String newPackagesContents = oldPackagesContents.replaceFirst('$appName:lib/', '$appName:$kMultiRootScheme:/'); 53 final String generatedPackagesPath = fs.path.setExtension(PackageMap.globalPackagesPath, '.generated'); 54 fs.file(generatedPackagesPath).writeAsStringSync(newPackagesContents); 55 } 56} 57 58class UnsupportedCodeGenerator extends CodeGenerator { 59 const UnsupportedCodeGenerator(); 60 61 @override 62 Future<void> generateBuildScript(FlutterProject flutterProject) { 63 throw UnsupportedError('build_runner is not currently supported.'); 64 } 65 66 @override 67 Future<CodegenDaemon> daemon(FlutterProject flutterProject) { 68 throw UnsupportedError('build_runner is not currently supported.'); 69 } 70} 71 72abstract class CodegenDaemon { 73 /// Whether the previously enqueued build was successful. 74 Stream<CodegenStatus> get buildResults; 75 76 CodegenStatus get lastStatus; 77 78 /// Starts a new build. 79 void startBuild(); 80} 81 82/// An implementation of the [KernelCompiler] which delegates to build_runner. 83/// 84/// Only a subset of the arguments provided to the [KernelCompiler] are 85/// supported here. Using the build pipeline implies a fixed multiroot 86/// filesystem and requires a pubspec. 87/// 88/// This is only safe to use if [experimentalBuildEnabled] is true. 89class CodeGeneratingKernelCompiler implements KernelCompiler { 90 const CodeGeneratingKernelCompiler(); 91 92 static const KernelCompiler _delegate = KernelCompiler(); 93 94 @override 95 Future<CompilerOutput> compile({ 96 String mainPath, 97 String outputFilePath, 98 bool linkPlatformKernelIn = false, 99 bool aot = false, 100 bool trackWidgetCreation, 101 List<String> extraFrontEndOptions, 102 bool targetProductVm = false, 103 // These arguments are currently unused. 104 String sdkRoot, 105 String packagesPath, 106 List<String> fileSystemRoots, 107 String fileSystemScheme, 108 String depFilePath, 109 TargetModel targetModel = TargetModel.flutter, 110 String initializeFromDill, 111 String platformDill, 112 }) async { 113 if (fileSystemRoots != null || fileSystemScheme != null || depFilePath != null || targetModel != null || sdkRoot != null || packagesPath != null) { 114 printTrace('fileSystemRoots, fileSystemScheme, depFilePath, targetModel,' 115 'sdkRoot, packagesPath are not supported when using the experimental ' 116 'build* pipeline'); 117 } 118 final FlutterProject flutterProject = FlutterProject.current(); 119 codeGenerator.updatePackages(flutterProject); 120 final CodegenDaemon codegenDaemon = await codeGenerator.daemon(flutterProject); 121 codegenDaemon.startBuild(); 122 await for (CodegenStatus codegenStatus in codegenDaemon.buildResults) { 123 if (codegenStatus == CodegenStatus.Failed) { 124 printError('Code generation failed, build may have compile errors.'); 125 break; 126 } 127 if (codegenStatus == CodegenStatus.Succeeded) { 128 break; 129 } 130 } 131 return _delegate.compile( 132 mainPath: mainPath, 133 outputFilePath: outputFilePath, 134 linkPlatformKernelIn: linkPlatformKernelIn, 135 aot: aot, 136 trackWidgetCreation: trackWidgetCreation, 137 extraFrontEndOptions: extraFrontEndOptions, 138 targetProductVm: targetProductVm, 139 sdkRoot: sdkRoot, 140 packagesPath: PackageMap.globalGeneratedPackagesPath, 141 fileSystemRoots: <String>[ 142 fs.path.join(flutterProject.generated.path, 'lib${platform.pathSeparator}'), 143 fs.path.join(flutterProject.directory.path, 'lib${platform.pathSeparator}'), 144 ], 145 fileSystemScheme: kMultiRootScheme, 146 depFilePath: depFilePath, 147 targetModel: targetModel, 148 initializeFromDill: initializeFromDill, 149 ); 150 } 151} 152 153/// An implementation of a [ResidentCompiler] which runs a [BuildRunner] before 154/// talking to the CFE. 155class CodeGeneratingResidentCompiler implements ResidentCompiler { 156 CodeGeneratingResidentCompiler._(this._residentCompiler, this._codegenDaemon, this._flutterProject); 157 158 /// Creates a new [ResidentCompiler] and configures a [BuildDaemonClient] to 159 /// run builds. 160 /// 161 /// If `runCold` is true, then no codegen daemon will be created. Instead the 162 /// compiler will only be initialized with the correct configuration for 163 /// codegen mode. 164 static Future<ResidentCompiler> create({ 165 @required FlutterProject flutterProject, 166 bool trackWidgetCreation = false, 167 CompilerMessageConsumer compilerMessageConsumer = printError, 168 bool unsafePackageSerialization = false, 169 String outputPath, 170 String initializeFromDill, 171 bool runCold = false, 172 }) async { 173 codeGenerator.updatePackages(flutterProject); 174 final ResidentCompiler residentCompiler = ResidentCompiler( 175 artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath), 176 trackWidgetCreation: trackWidgetCreation, 177 packagesPath: PackageMap.globalGeneratedPackagesPath, 178 fileSystemRoots: <String>[ 179 fs.path.join(flutterProject.generated.path, 'lib${platform.pathSeparator}'), 180 fs.path.join(flutterProject.directory.path, 'lib${platform.pathSeparator}'), 181 ], 182 fileSystemScheme: kMultiRootScheme, 183 targetModel: TargetModel.flutter, 184 unsafePackageSerialization: unsafePackageSerialization, 185 initializeFromDill: initializeFromDill, 186 ); 187 if (runCold) { 188 return residentCompiler; 189 } 190 final CodegenDaemon codegenDaemon = await codeGenerator.daemon(flutterProject); 191 codegenDaemon.startBuild(); 192 final CodegenStatus status = await codegenDaemon.buildResults.firstWhere((CodegenStatus status) { 193 return status == CodegenStatus.Succeeded || status == CodegenStatus.Failed; 194 }); 195 if (status == CodegenStatus.Failed) { 196 printError('Code generation failed, build may have compile errors.'); 197 } 198 return CodeGeneratingResidentCompiler._(residentCompiler, codegenDaemon, flutterProject); 199 } 200 201 final ResidentCompiler _residentCompiler; 202 final CodegenDaemon _codegenDaemon; 203 final FlutterProject _flutterProject; 204 205 @override 206 void accept() { 207 _residentCompiler.accept(); 208 } 209 210 @override 211 Future<CompilerOutput> compileExpression(String expression, List<String> definitions, List<String> typeDefinitions, String libraryUri, String klass, bool isStatic) { 212 return _residentCompiler.compileExpression(expression, definitions, typeDefinitions, libraryUri, klass, isStatic); 213 } 214 215 @override 216 Future<CompilerOutput> recompile(String mainPath, List<Uri> invalidatedFiles, {String outputPath, String packagesFilePath}) async { 217 if (_codegenDaemon.lastStatus != CodegenStatus.Succeeded && _codegenDaemon.lastStatus != CodegenStatus.Failed) { 218 await _codegenDaemon.buildResults.firstWhere((CodegenStatus status) { 219 return status == CodegenStatus.Succeeded || status == CodegenStatus.Failed; 220 }); 221 } 222 if (_codegenDaemon.lastStatus == CodegenStatus.Failed) { 223 printError('Code generation failed, build may have compile errors.'); 224 } 225 // Update the generated packages file if the original packages file has changes. 226 if (fs.statSync(PackageMap.globalPackagesPath).modified.millisecondsSinceEpoch > 227 fs.statSync(PackageMap.globalGeneratedPackagesPath).modified.millisecondsSinceEpoch) { 228 codeGenerator.updatePackages(_flutterProject); 229 invalidatedFiles.add(fs.file(PackageMap.globalGeneratedPackagesPath).uri); 230 } 231 return _residentCompiler.recompile( 232 mainPath, 233 invalidatedFiles, 234 outputPath: outputPath, 235 packagesFilePath: PackageMap.globalGeneratedPackagesPath, 236 ); 237 } 238 239 @override 240 Future<CompilerOutput> reject() { 241 return _residentCompiler.reject(); 242 } 243 244 @override 245 void reset() { 246 _residentCompiler.reset(); 247 } 248 249 @override 250 Future<void> shutdown() { 251 return _residentCompiler.shutdown(); 252 } 253} 254 255/// The current status of a codegen build. 256enum CodegenStatus { 257 /// The build has started running. 258 /// 259 /// If this is the current status when running a hot reload, an additional build does 260 /// not need to be started. 261 Started, 262 /// The build succeeded. 263 Succeeded, 264 /// The build failed. 265 Failed 266} 267