• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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