codegen.dart 9.93 KB
Newer Older
1 2 3 4 5 6 7 8 9 10
// 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 'package:meta/meta.dart';

import 'artifacts.dart';
import 'base/context.dart';
import 'base/file_system.dart';
import 'base/platform.dart';
11
import 'build_info.dart';
12
import 'compile.dart';
13
import 'dart/package_map.dart';
14 15 16
import 'globals.dart';
import 'project.dart';

Chris Bracken's avatar
Chris Bracken committed
17
// Arbitrarily chosen multi-root file scheme. This is used to configure the
18 19 20
// frontend_server to resolve a package uri to multiple filesystem directories.
// In this case, the source directory and a generated directory.
const String kMultiRootScheme = 'org-dartlang-app';
21 22 23 24 25

/// The [CodeGenerator] instance.
///
/// If [experimentalBuildEnabled] is false, this will contain an unsupported
/// implementation.
26
CodeGenerator get codeGenerator => context.get<CodeGenerator>();
27 28 29 30 31 32 33 34 35 36 37 38 39

/// 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.
abstract class CodeGenerator {
  const CodeGenerator();

  /// Starts a persistent code generting daemon.
  ///
  /// The defines of the daemon command are the arguments required in the
  /// flutter_build kernel builder.
40
  Future<CodegenDaemon> daemon(FlutterProject flutterProject);
41 42 43

  // Generates a synthetic package under .dart_tool/flutter_tool which is in turn
  // used to generate a build script.
44
  Future<void> generateBuildScript(FlutterProject flutterProject);
45 46 47 48 49 50 51 52 53 54 55 56

  /// Create generated packages file which adds a multi-root scheme to the user's
  /// project directory. Currently we only replace the root package with a multiroot
  /// scheme. To support codegen on arbitrary packages we would need to do
  /// this for each dependency.
  void updatePackages(FlutterProject flutterProject) {
    final String oldPackagesContents = fs.file(PackageMap.globalPackagesPath).readAsStringSync();
    final String appName = flutterProject.manifest.appName;
    final String newPackagesContents = oldPackagesContents.replaceFirst('$appName:lib/', '$appName:$kMultiRootScheme:/');
    final String generatedPackagesPath = fs.path.setExtension(PackageMap.globalPackagesPath, '.generated');
    fs.file(generatedPackagesPath).writeAsStringSync(newPackagesContents);
  }
57 58 59 60 61 62
}

class UnsupportedCodeGenerator extends CodeGenerator {
  const UnsupportedCodeGenerator();

  @override
63
  Future<void> generateBuildScript(FlutterProject flutterProject) {
64 65 66 67
    throw UnsupportedError('build_runner is not currently supported.');
  }

  @override
68
  Future<CodegenDaemon> daemon(FlutterProject flutterProject) {
69 70 71 72 73 74
    throw UnsupportedError('build_runner is not currently supported.');
  }
}

abstract class CodegenDaemon {
  /// Whether the previously enqueued build was successful.
75
  Stream<CodegenStatus> get buildResults;
76

77 78
  CodegenStatus get lastStatus;

79 80 81 82 83 84 85 86 87 88 89 90
  /// Starts a new build.
  void startBuild();
}

/// An implementation of the [KernelCompiler] which delegates to build_runner.
///
/// Only a subset of the arguments provided to the [KernelCompiler] are
/// supported here. Using the build pipeline implies a fixed multiroot
/// filesystem and requires a pubspec.
class CodeGeneratingKernelCompiler implements KernelCompiler {
  const CodeGeneratingKernelCompiler();

91 92
  static const KernelCompiler _delegate = KernelCompiler();

93 94 95 96 97 98
  @override
  Future<CompilerOutput> compile({
    String mainPath,
    String outputFilePath,
    bool linkPlatformKernelIn = false,
    bool aot = false,
99
    @required BuildMode buildMode,
100
    bool causalAsyncStacks = true,
101 102 103 104 105 106 107 108 109
    bool trackWidgetCreation,
    List<String> extraFrontEndOptions,
    // These arguments are currently unused.
    String sdkRoot,
    String packagesPath,
    List<String> fileSystemRoots,
    String fileSystemScheme,
    String depFilePath,
    TargetModel targetModel = TargetModel.flutter,
110
    String initializeFromDill,
111
    String platformDill,
112 113 114 115 116 117
  }) async {
    if (fileSystemRoots != null || fileSystemScheme != null || depFilePath != null || targetModel != null || sdkRoot != null || packagesPath != null) {
      printTrace('fileSystemRoots, fileSystemScheme, depFilePath, targetModel,'
        'sdkRoot, packagesPath are not supported when using the experimental '
        'build* pipeline');
    }
118
    final FlutterProject flutterProject = FlutterProject.current();
119
    codeGenerator.updatePackages(flutterProject);
120 121 122 123
    final CodegenDaemon codegenDaemon = await codeGenerator.daemon(flutterProject);
    codegenDaemon.startBuild();
    await for (CodegenStatus codegenStatus in codegenDaemon.buildResults) {
      if (codegenStatus == CodegenStatus.Failed) {
124 125
        printError('Code generation failed, build may have compile errors.');
        break;
126 127 128
      }
      if (codegenStatus == CodegenStatus.Succeeded) {
        break;
129 130
      }
    }
131 132 133 134 135
    return _delegate.compile(
      mainPath: mainPath,
      outputFilePath: outputFilePath,
      linkPlatformKernelIn: linkPlatformKernelIn,
      aot: aot,
136
      buildMode: buildMode,
137
      causalAsyncStacks: causalAsyncStacks,
138 139 140 141 142 143 144 145
      trackWidgetCreation: trackWidgetCreation,
      extraFrontEndOptions: extraFrontEndOptions,
      sdkRoot: sdkRoot,
      packagesPath: PackageMap.globalGeneratedPackagesPath,
      fileSystemRoots: <String>[
        fs.path.join(flutterProject.generated.path, 'lib${platform.pathSeparator}'),
        fs.path.join(flutterProject.directory.path, 'lib${platform.pathSeparator}'),
      ],
146
      fileSystemScheme: kMultiRootScheme,
147 148
      depFilePath: depFilePath,
      targetModel: targetModel,
149
      initializeFromDill: initializeFromDill,
150
    );
151 152 153 154 155 156
  }
}

/// An implementation of a [ResidentCompiler] which runs a [BuildRunner] before
/// talking to the CFE.
class CodeGeneratingResidentCompiler implements ResidentCompiler {
157
  CodeGeneratingResidentCompiler._(this._residentCompiler, this._codegenDaemon, this._flutterProject);
158 159 160

  /// Creates a new [ResidentCompiler] and configures a [BuildDaemonClient] to
  /// run builds.
161 162 163 164 165
  ///
  /// If `runCold` is true, then no codegen daemon will be created. Instead the
  /// compiler will only be initialized with the correct configuration for
  /// codegen mode.
  static Future<ResidentCompiler> create({
166
    @required FlutterProject flutterProject,
167
    @required BuildMode buildMode,
168 169 170
    bool trackWidgetCreation = false,
    CompilerMessageConsumer compilerMessageConsumer = printError,
    bool unsafePackageSerialization = false,
171 172
    String outputPath,
    String initializeFromDill,
173
    bool runCold = false,
174
  }) async {
175
    codeGenerator.updatePackages(flutterProject);
176 177
    final ResidentCompiler residentCompiler = ResidentCompiler(
      artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath),
178
      buildMode: buildMode,
179
      trackWidgetCreation: trackWidgetCreation,
180
      packagesPath: PackageMap.globalGeneratedPackagesPath,
181
      fileSystemRoots: <String>[
182
        fs.path.join(flutterProject.generated.path, 'lib${platform.pathSeparator}'),
183
        fs.path.join(flutterProject.directory.path, 'lib${platform.pathSeparator}'),
184
      ],
185
      fileSystemScheme: kMultiRootScheme,
186 187
      targetModel: TargetModel.flutter,
      unsafePackageSerialization: unsafePackageSerialization,
188
      initializeFromDill: initializeFromDill,
189
    );
190 191 192 193 194 195 196 197 198 199 200
    if (runCold) {
      return residentCompiler;
    }
    final CodegenDaemon codegenDaemon = await codeGenerator.daemon(flutterProject);
    codegenDaemon.startBuild();
    final CodegenStatus status = await codegenDaemon.buildResults.firstWhere((CodegenStatus status) {
      return status == CodegenStatus.Succeeded || status == CodegenStatus.Failed;
    });
    if (status == CodegenStatus.Failed) {
      printError('Code generation failed, build may have compile errors.');
    }
201
    return CodeGeneratingResidentCompiler._(residentCompiler, codegenDaemon, flutterProject);
202 203 204 205
  }

  final ResidentCompiler _residentCompiler;
  final CodegenDaemon _codegenDaemon;
206
  final FlutterProject _flutterProject;
207 208 209 210 211 212 213 214 215 216 217 218

  @override
  void accept() {
    _residentCompiler.accept();
  }

  @override
  Future<CompilerOutput> compileExpression(String expression, List<String> definitions, List<String> typeDefinitions, String libraryUri, String klass, bool isStatic) {
    return _residentCompiler.compileExpression(expression, definitions, typeDefinitions, libraryUri, klass, isStatic);
  }

  @override
219
  Future<CompilerOutput> recompile(String mainPath, List<Uri> invalidatedFiles, {String outputPath, String packagesFilePath}) async {
220 221
    if (_codegenDaemon.lastStatus != CodegenStatus.Succeeded && _codegenDaemon.lastStatus != CodegenStatus.Failed) {
      await _codegenDaemon.buildResults.firstWhere((CodegenStatus status) {
222
        return status == CodegenStatus.Succeeded || status == CodegenStatus.Failed;
223 224 225
      });
    }
    if (_codegenDaemon.lastStatus == CodegenStatus.Failed) {
226 227 228 229 230 231 232
      printError('Code generation failed, build may have compile errors.');
    }
    // Update the generated packages file if the original packages file has changes.
    if (fs.statSync(PackageMap.globalPackagesPath).modified.millisecondsSinceEpoch >
        fs.statSync(PackageMap.globalGeneratedPackagesPath).modified.millisecondsSinceEpoch) {
      codeGenerator.updatePackages(_flutterProject);
      invalidatedFiles.add(fs.file(PackageMap.globalGeneratedPackagesPath).uri);
233
    }
234 235 236 237
    return _residentCompiler.recompile(
      mainPath,
      invalidatedFiles,
      outputPath: outputPath,
238
      packagesFilePath: PackageMap.globalGeneratedPackagesPath,
239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256
    );
  }

  @override
  Future<CompilerOutput> reject() {
    return _residentCompiler.reject();
  }

  @override
  void reset() {
    _residentCompiler.reset();
  }

  @override
  Future<void> shutdown() {
    return _residentCompiler.shutdown();
  }
}
257 258 259 260 261 262 263 264 265 266 267 268 269

/// The current status of a codegen build.
enum CodegenStatus {
  /// The build has started running.
  ///
  /// If this is the current status when running a hot reload, an additional build does
  /// not need to be started.
  Started,
  /// The build succeeded.
  Succeeded,
  /// The build failed.
  Failed
}