common.dart 15.3 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

5 6
import 'package:package_config/package_config.dart';

7 8 9
import '../../artifacts.dart';
import '../../base/build.dart';
import '../../base/file_system.dart';
10
import '../../base/io.dart';
11 12
import '../../build_info.dart';
import '../../compile.dart';
13
import '../../dart/package_map.dart';
14
import '../../globals.dart' as globals show xcode;
15
import '../build_system.dart';
16
import '../depfile.dart';
17
import '../exceptions.dart';
18
import 'assets.dart';
19
import 'dart_plugin_registrant.dart';
20
import 'icon_tree_shaker.dart';
21
import 'localizations.dart';
22
import 'shader_compiler.dart';
23

24
/// Copies the pre-built flutter bundle.
25 26 27 28 29 30 31 32 33 34 35 36
// This is a one-off rule for implementing build bundle in terms of assemble.
class CopyFlutterBundle extends Target {
  const CopyFlutterBundle();

  @override
  String get name => 'copy_flutter_bundle';

  @override
  List<Source> get inputs => const <Source>[
    Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug),
    Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
    Source.pattern('{BUILD_DIR}/app.dill'),
37
    ...IconTreeShaker.inputs,
38
    ...ShaderCompiler.inputs,
39 40 41 42 43 44 45
  ];

  @override
  List<Source> get outputs => const <Source>[
    Source.pattern('{OUTPUT_DIR}/vm_snapshot_data'),
    Source.pattern('{OUTPUT_DIR}/isolate_snapshot_data'),
    Source.pattern('{OUTPUT_DIR}/kernel_blob.bin'),
46 47 48 49
  ];

  @override
  List<String> get depfiles => <String>[
50
    'flutter_assets.d',
51 52 53 54
  ];

  @override
  Future<void> build(Environment environment) async {
55 56
    final String? buildModeEnvironment = environment.defines[kBuildMode];
    if (buildModeEnvironment == null) {
57 58
      throw MissingDefineException(kBuildMode, 'copy_flutter_bundle');
    }
59
    final BuildMode buildMode = getBuildModeForName(buildModeEnvironment);
60 61 62 63
    environment.outputDir.createSync(recursive: true);

    // Only copy the prebuilt runtimes and kernel blob in debug mode.
    if (buildMode == BuildMode.debug) {
64 65
      final String vmSnapshotData = environment.artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug);
      final String isolateSnapshotData = environment.artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug);
66 67
      environment.buildDir.childFile('app.dill')
          .copySync(environment.outputDir.childFile('kernel_blob.bin').path);
68
      environment.fileSystem.file(vmSnapshotData)
69
          .copySync(environment.outputDir.childFile('vm_snapshot_data').path);
70
      environment.fileSystem.file(isolateSnapshotData)
71 72
          .copySync(environment.outputDir.childFile('isolate_snapshot_data').path);
    }
73 74 75 76
    final Depfile assetDepfile = await copyAssets(
      environment,
      environment.outputDir,
      targetPlatform: TargetPlatform.android,
77
      buildMode: buildMode,
78
      shaderTarget: ShaderTarget.sksl,
79
    );
80
    final DepfileService depfileService = DepfileService(
81 82
      fileSystem: environment.fileSystem,
      logger: environment.logger,
83 84 85 86 87
    );
    depfileService.writeToFile(
      assetDepfile,
      environment.buildDir.childFile('flutter_assets.d'),
    );
88 89 90 91 92 93 94 95
  }

  @override
  List<Target> get dependencies => const <Target>[
    KernelSnapshot(),
  ];
}

96
/// Copies the pre-built flutter bundle for release mode.
97 98 99 100 101 102 103
class ReleaseCopyFlutterBundle extends CopyFlutterBundle {
  const ReleaseCopyFlutterBundle();

  @override
  String get name => 'release_flutter_bundle';

  @override
104
  List<Source> get inputs => const <Source>[];
105 106

  @override
107 108 109 110 111
  List<Source> get outputs => const <Source>[];

  @override
  List<String> get depfiles => const <String>[
    'flutter_assets.d',
112 113 114 115 116 117
  ];

  @override
  List<Target> get dependencies => const <Target>[];
}

118
/// Generate a snapshot of the dart code used in the program.
119 120 121
///
/// Note that this target depends on the `.dart_tool/package_config.json` file
/// even though it is not listed as an input. Pub inserts a timestamp into
122
/// the file which causes unnecessary rebuilds, so instead a subset of the contents
123
/// are used an input instead.
124 125 126 127 128 129 130 131
class KernelSnapshot extends Target {
  const KernelSnapshot();

  @override
  String get name => 'kernel_snapshot';

  @override
  List<Source> get inputs => const <Source>[
132
    Source.pattern('{PROJECT_DIR}/.dart_tool/package_config_subset'),
133
    Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/common.dart'),
134
    Source.artifact(Artifact.platformKernelDill),
135
    Source.hostArtifact(HostArtifact.engineDartBinary),
136 137 138 139
    Source.artifact(Artifact.frontendServerSnapshotForEngineDartSdk),
  ];

  @override
140 141 142 143 144
  List<Source> get outputs => const <Source>[];

  @override
  List<String> get depfiles => <String>[
    'kernel_snapshot.d',
145 146 147
  ];

  @override
148 149
  List<Target> get dependencies => const <Target>[
    GenerateLocalizationsTarget(),
150
    DartPluginRegistrantTarget(),
151
  ];
152 153

  @override
154
  Future<void> build(Environment environment) async {
155 156 157 158 159
    final KernelCompiler compiler = KernelCompiler(
      fileSystem: environment.fileSystem,
      logger: environment.logger,
      processManager: environment.processManager,
      artifacts: environment.artifacts,
160
      fileSystemRoots: <String>[],
161
    );
162 163
    final String? buildModeEnvironment = environment.defines[kBuildMode];
    if (buildModeEnvironment == null) {
164 165
      throw MissingDefineException(kBuildMode, 'kernel_snapshot');
    }
166 167
    final String? targetPlatformEnvironment = environment.defines[kTargetPlatform];
    if (targetPlatformEnvironment == null) {
168 169
      throw MissingDefineException(kTargetPlatform, 'kernel_snapshot');
    }
170
    final BuildMode buildMode = getBuildModeForName(buildModeEnvironment);
171
    final String targetFile = environment.defines[kTargetFile] ?? environment.fileSystem.path.join('lib', 'main.dart');
172 173 174
    final File packagesFile = environment.projectDir
      .childDirectory('.dart_tool')
      .childFile('package_config.json');
175
    final String targetFileAbsolute = environment.fileSystem.file(targetFile).absolute.path;
176 177
    // everything besides 'false' is considered to be enabled.
    final bool trackWidgetCreation = environment.defines[kTrackWidgetCreation] != 'false';
178
    final TargetPlatform targetPlatform = getTargetPlatformForName(targetPlatformEnvironment);
179

180
    // This configuration is all optional.
181
    final List<String> extraFrontEndOptions = decodeCommaSeparated(environment.defines, kExtraFrontEndOptions);
182 183
    final List<String>? fileSystemRoots = environment.defines[kFileSystemRoots]?.split(',');
    final String? fileSystemScheme = environment.defines[kFileSystemScheme];
184

185 186 187 188 189
    TargetModel targetModel = TargetModel.flutter;
    if (targetPlatform == TargetPlatform.fuchsia_x64 ||
        targetPlatform == TargetPlatform.fuchsia_arm64) {
      targetModel = TargetModel.flutterRunner;
    }
190 191 192 193 194
    // Force linking of the platform for desktop embedder targets since these
    // do not correctly load the core snapshots in debug mode.
    // See https://github.com/flutter/flutter/issues/44724
    bool forceLinkPlatform;
    switch (targetPlatform) {
195
      case TargetPlatform.darwin:
196 197
      case TargetPlatform.windows_x64:
      case TargetPlatform.linux_x64:
198 199
        forceLinkPlatform = true;
        break;
200 201 202 203 204 205 206 207 208 209 210
      case TargetPlatform.android:
      case TargetPlatform.android_arm:
      case TargetPlatform.android_arm64:
      case TargetPlatform.android_x64:
      case TargetPlatform.android_x86:
      case TargetPlatform.fuchsia_arm64:
      case TargetPlatform.fuchsia_x64:
      case TargetPlatform.ios:
      case TargetPlatform.linux_arm64:
      case TargetPlatform.tester:
      case TargetPlatform.web_javascript:
211
        forceLinkPlatform = false;
212
        break;
213
    }
214

215
    final PackageConfig packageConfig = await loadPackageConfigWithLogging(
216
      packagesFile,
217
      logger: environment.logger,
218 219
    );

220
    final CompilerOutput? output = await compiler.compile(
221
      sdkRoot: environment.artifacts.getArtifactPath(
222 223 224 225
        Artifact.flutterPatchedSdkPath,
        platform: targetPlatform,
        mode: buildMode,
      ),
226
      aot: buildMode.isPrecompiled,
227
      buildMode: buildMode,
228
      trackWidgetCreation: trackWidgetCreation && buildMode != BuildMode.release,
229
      targetModel: targetModel,
230
      outputFilePath: environment.buildDir.childFile('app.dill').path,
231 232
      initializeFromDill: buildMode.isPrecompiled ? null :
          environment.buildDir.childFile('app.dill').path,
233
      packagesPath: packagesFile.path,
234
      linkPlatformKernelIn: forceLinkPlatform || buildMode.isPrecompiled,
235
      mainPath: targetFileAbsolute,
236
      depFilePath: environment.buildDir.childFile('kernel_snapshot.d').path,
237 238 239
      extraFrontEndOptions: extraFrontEndOptions,
      fileSystemRoots: fileSystemRoots,
      fileSystemScheme: fileSystemScheme,
240
      dartDefines: decodeDartDefines(environment.defines, kDartDefines),
241
      packageConfig: packageConfig,
242 243
      buildDir: environment.buildDir,
      checkDartPluginRegistry: environment.generateDartPluginRegistry,
244
    );
245
    if (output == null || output.errorCount != 0) {
246
      throw Exception();
247
    }
248
  }
249
}
250

251 252 253 254
/// Supports compiling a dart kernel file to an ELF binary.
abstract class AotElfBase extends Target {
  const AotElfBase();

255 256 257
  @override
  String get analyticsName => 'android_aot';

258
  @override
259
  Future<void> build(Environment environment) async {
260
    final AOTSnapshotter snapshotter = AOTSnapshotter(
261 262
      fileSystem: environment.fileSystem,
      logger: environment.logger,
263
      xcode: globals.xcode!,
264 265
      processManager: environment.processManager,
      artifacts: environment.artifacts,
266
    );
267
    final String outputPath = environment.buildDir.path;
268 269
    final String? buildModeEnvironment = environment.defines[kBuildMode];
    if (buildModeEnvironment == null) {
270 271
      throw MissingDefineException(kBuildMode, 'aot_elf');
    }
272 273
    final String? targetPlatformEnvironment = environment.defines[kTargetPlatform];
    if (targetPlatformEnvironment == null) {
274 275
      throw MissingDefineException(kTargetPlatform, 'aot_elf');
    }
276
    final List<String> extraGenSnapshotOptions = decodeCommaSeparated(environment.defines, kExtraGenSnapshotOptions);
277 278 279
    final BuildMode buildMode = getBuildModeForName(buildModeEnvironment);
    final TargetPlatform targetPlatform = getTargetPlatformForName(targetPlatformEnvironment);
    final String? splitDebugInfo = environment.defines[kSplitDebugInfo];
280
    final bool dartObfuscation = environment.defines[kDartObfuscation] == 'true';
281
    final String? codeSizeDirectory = environment.defines[kCodeSizeDirectory];
282 283 284 285 286 287 288 289 290 291 292 293

    if (codeSizeDirectory != null) {
      final File codeSizeFile = environment.fileSystem
        .directory(codeSizeDirectory)
        .childFile('snapshot.${environment.defines[kTargetPlatform]}.json');
      final File precompilerTraceFile = environment.fileSystem
        .directory(codeSizeDirectory)
        .childFile('trace.${environment.defines[kTargetPlatform]}.json');
      extraGenSnapshotOptions.add('--write-v8-snapshot-profile-to=${codeSizeFile.path}');
      extraGenSnapshotOptions.add('--trace-precompiler-to=${precompilerTraceFile.path}');
    }

294 295 296
    final int snapshotExitCode = await snapshotter.build(
      platform: targetPlatform,
      buildMode: buildMode,
297
      mainPath: environment.buildDir.childFile('app.dill').path,
298
      outputPath: outputPath,
299
      extraGenSnapshotOptions: extraGenSnapshotOptions,
300 301
      splitDebugInfo: splitDebugInfo,
      dartObfuscation: dartObfuscation,
302 303 304 305 306 307 308 309
    );
    if (snapshotExitCode != 0) {
      throw Exception('AOT snapshotter exited with code $snapshotExitCode');
    }
  }
}

/// Generate an ELF binary from a dart kernel file in profile mode.
310
class AotElfProfile extends AotElfBase {
311
  const AotElfProfile(this.targetPlatform);
312 313 314 315 316

  @override
  String get name => 'aot_elf_profile';

  @override
317
  List<Source> get inputs => <Source>[
318
    const Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/common.dart'),
319
    const Source.pattern('{BUILD_DIR}/app.dill'),
320
    const Source.hostArtifact(HostArtifact.engineDartBinary),
321
    const Source.artifact(Artifact.skyEnginePath),
322
    Source.artifact(Artifact.genSnapshot,
323
      platform: targetPlatform,
324 325
      mode: BuildMode.profile,
    ),
326 327 328 329
  ];

  @override
  List<Source> get outputs => const <Source>[
330
    Source.pattern('{BUILD_DIR}/app.so'),
331 332 333 334 335 336
  ];

  @override
  List<Target> get dependencies => const <Target>[
    KernelSnapshot(),
  ];
337

338
  final TargetPlatform targetPlatform;
339
}
340 341

/// Generate an ELF binary from a dart kernel file in release mode.
342
class AotElfRelease extends AotElfBase {
343
  const AotElfRelease(this.targetPlatform);
344 345 346 347 348

  @override
  String get name => 'aot_elf_release';

  @override
349
  List<Source> get inputs => <Source>[
350
    const Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/common.dart'),
351
    const Source.pattern('{BUILD_DIR}/app.dill'),
352
    const Source.hostArtifact(HostArtifact.engineDartBinary),
353
    const Source.artifact(Artifact.skyEnginePath),
354
    Source.artifact(Artifact.genSnapshot,
355
      platform: targetPlatform,
356 357
      mode: BuildMode.release,
    ),
358
  ];
359

360 361 362 363
  @override
  List<Source> get outputs => const <Source>[
    Source.pattern('{BUILD_DIR}/app.so'),
  ];
364

365 366 367 368
  @override
  List<Target> get dependencies => const <Target>[
    KernelSnapshot(),
  ];
369

370
  final TargetPlatform targetPlatform;
371
}
372

373
/// Copies the pre-built flutter aot bundle.
374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396
// This is a one-off rule for implementing build aot in terms of assemble.
abstract class CopyFlutterAotBundle extends Target {
  const CopyFlutterAotBundle();

  @override
  List<Source> get inputs => const <Source>[
    Source.pattern('{BUILD_DIR}/app.so'),
  ];

  @override
  List<Source> get outputs => const <Source>[
    Source.pattern('{OUTPUT_DIR}/app.so'),
  ];

  @override
  Future<void> build(Environment environment) async {
    final File outputFile = environment.outputDir.childFile('app.so');
    if (!outputFile.parent.existsSync()) {
      outputFile.parent.createSync(recursive: true);
    }
    environment.buildDir.childFile('app.so').copySync(outputFile.path);
  }
}
397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441

/// Lipo CLI tool wrapper shared by iOS and macOS builds.
class Lipo {
  /// Static only.
  Lipo._();

  /// Create a "fat" binary by combining multiple architecture-specific ones.
  /// `skipMissingInputs` can be changed to `true` to first check whether
  /// the expected input paths exist and ignore the command if they don't.
  /// Otherwise, `lipo` would fail if the given paths didn't exist.
  static Future<void> create(
    Environment environment,
    List<DarwinArch> darwinArchs, {
    required String relativePath,
    required String inputDir,
    bool skipMissingInputs = false,
  }) async {

    final String resultPath = environment.fileSystem.path.join(environment.buildDir.path, relativePath);
    environment.fileSystem.directory(resultPath).parent.createSync(recursive: true);

    Iterable<String> inputPaths = darwinArchs.map(
      (DarwinArch iosArch) => environment.fileSystem.path.join(inputDir, getNameForDarwinArch(iosArch), relativePath)
    );
    if (skipMissingInputs) {
      inputPaths = inputPaths.where(environment.fileSystem.isFileSync);
      if (inputPaths.isEmpty) {
        return;
      }
    }

    final List<String> lipoArgs = <String>[
      'lipo',
      ...inputPaths,
      '-create',
      '-output',
      resultPath,
    ];

    final ProcessResult result = await environment.processManager.run(lipoArgs);
    if (result.exitCode != 0) {
      throw Exception('lipo exited with code ${result.exitCode}.\n${result.stderr}');
    }
  }
}