build.dart 12.5 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
import 'dart:async';
6

7
import 'package:meta/meta.dart';
8

9
import '../artifacts.dart';
10
import '../build_info.dart';
11
import '../bundle.dart';
12
import '../compile.dart';
13
import '../globals.dart' as globals;
14
import '../macos/xcode.dart';
15
import '../project.dart';
16
import '../reporting/reporting.dart';
17

18
import 'context.dart';
19
import 'file_system.dart';
20 21
import 'process.dart';

22
GenSnapshot get genSnapshot => context.get<GenSnapshot>();
23 24 25

/// A snapshot build configuration.
class SnapshotType {
26 27
  SnapshotType(this.platform, this.mode)
    : assert(mode != null);
28 29 30

  final TargetPlatform platform;
  final BuildMode mode;
31 32 33

  @override
  String toString() => '$platform $mode';
34 35 36 37 38 39
}

/// Interface to the gen_snapshot command-line tool.
class GenSnapshot {
  const GenSnapshot();

40
  static String getSnapshotterPath(SnapshotType snapshotType) {
41
    return globals.artifacts.getArtifactPath(
42
        Artifact.genSnapshot, platform: snapshotType.platform, mode: snapshotType.mode);
43 44
  }

45 46 47 48 49 50 51 52 53 54
  /// Ignored warning messages from gen_snapshot.
  static const Set<String> kIgnoredWarnings = <String>{
    // --strip on elf snapshot.
    'Warning: Generating ELF library without DWARF debugging information.',
    // --strip on ios-assembly snapshot.
    'Warning: Generating assembly code without DWARF debugging information.',
    // A fun two-part message with spaces for obfuscation.
    'Warning: This VM has been configured to obfuscate symbol information which violates the Dart standard.',
    '         See dartbug.com/30524 for more information.',
  };
55 56
  Future<int> run({
    @required SnapshotType snapshotType,
57
    DarwinArch darwinArch,
58
    Iterable<String> additionalArgs = const <String>[],
59 60
  }) {
    final List<String> args = <String>[
61 62
      ...additionalArgs,
    ];
63

64
    String snapshotterPath = getSnapshotterPath(snapshotType);
65

66 67
    // iOS has a separate gen_snapshot for armv7 and arm64 in the same,
    // directory. So we need to select the right one.
68
    if (snapshotType.platform == TargetPlatform.ios) {
69
      snapshotterPath += '_' + getNameForDarwinArch(darwinArch);
70 71
    }

72 73
    return processUtils.stream(
      <String>[snapshotterPath, ...args],
74
      mapFunction: (String line) =>  kIgnoredWarnings.contains(line) ? null : line,
75
    );
76 77
  }
}
78

79
class AOTSnapshotter {
80 81 82 83 84 85 86
  AOTSnapshotter({this.reportTimings = false});

  /// If true then AOTSnapshotter would report timings for individual building
  /// steps (Dart front-end parsing and snapshot generation) in a stable
  /// machine readable form. See [AOTSnapshotter._timedStep].
  final bool reportTimings;

87
  /// Builds an architecture-specific ahead-of-time compiled snapshot of the specified script.
88
  Future<int> build({
89 90 91 92 93
    @required TargetPlatform platform,
    @required BuildMode buildMode,
    @required String mainPath,
    @required String packagesPath,
    @required String outputPath,
94
    DarwinArch darwinArch,
95
    List<String> extraGenSnapshotOptions = const <String>[],
96
    @required bool bitcode,
97
    @required String splitDebugInfo,
98
    @required bool dartObfuscation,
xster's avatar
xster committed
99
    bool quiet = false,
100
  }) async {
101
    if (bitcode && platform != TargetPlatform.ios) {
102
      globals.printError('Bitcode is only supported for iOS.');
103 104 105
      return 1;
    }

106
    if (!_isValidAotPlatform(platform, buildMode)) {
107
      globals.printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
108
      return 1;
109
    }
110
    // TODO(cbracken): replace IOSArch with TargetPlatform.ios_{armv7,arm64}.
111
    assert(platform != TargetPlatform.ios || darwinArch != null);
112

113
    final Directory outputDir = globals.fs.directory(outputPath);
114 115
    outputDir.createSync(recursive: true);

116
    final List<String> genSnapshotArgs = <String>[
117
      '--deterministic',
118
    ];
119
    if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) {
120
      globals.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
121 122 123
      genSnapshotArgs.addAll(extraGenSnapshotOptions);
    }

124
    final String assembly = globals.fs.path.join(outputDir.path, 'snapshot_assembly.S');
125
    if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) {
126 127 128
      // Assembly AOT snapshot.
      genSnapshotArgs.add('--snapshot_kind=app-aot-assembly');
      genSnapshotArgs.add('--assembly=$assembly');
129
      genSnapshotArgs.add('--strip');
130
    } else {
131
      final String aotSharedLibrary = globals.fs.path.join(outputDir.path, 'app.so');
132
      genSnapshotArgs.add('--snapshot_kind=app-aot-elf');
133
      genSnapshotArgs.add('--elf=$aotSharedLibrary');
134
      genSnapshotArgs.add('--strip');
135 136
    }

137
    if (platform == TargetPlatform.android_arm || darwinArch == DarwinArch.armv7) {
138
      // Use softfp for Android armv7 devices.
Ian Hickson's avatar
Ian Hickson committed
139
      // This is the default for armv7 iOS builds, but harmless to set.
140
      // TODO(cbracken): eliminate this when we fix https://github.com/flutter/flutter/issues/17489
141 142
      genSnapshotArgs.add('--no-sim-use-hardfp');

143 144
      // Not supported by the Pixel in 32-bit mode.
      genSnapshotArgs.add('--no-use-integer-division');
145 146
    }

147
    // The name of the debug file must contain additional information about
148 149 150 151
    // the architecture, since a single build command may produce
    // multiple debug files.
    final String archName = getNameForTargetPlatform(platform, darwinArch: darwinArch);
    final String debugFilename = 'app.$archName.symbols';
152 153
    final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
    if (shouldSplitDebugInfo) {
154 155 156 157
      globals.fs.directory(splitDebugInfo)
        .createSync(recursive: true);
    }

158 159 160 161 162
    // Optimization arguments.
    genSnapshotArgs.addAll(<String>[
      // Faster async/await
      '--no-causal-async-stacks',
      '--lazy-async-stacks',
163
      if (shouldSplitDebugInfo) ...<String>[
164 165
        '--dwarf-stack-traces',
        '--save-debugging-info=${globals.fs.path.join(splitDebugInfo, debugFilename)}'
166 167 168
      ],
      if (dartObfuscation)
        '--obfuscate',
169 170
    ]);

171
    genSnapshotArgs.add(mainPath);
172

173
    final SnapshotType snapshotType = SnapshotType(platform, buildMode);
174 175 176
    final int genSnapshotExitCode =
      await _timedStep('snapshot(CompileTime)', 'aot-snapshot',
        () => genSnapshot.run(
177
      snapshotType: snapshotType,
178
      additionalArgs: genSnapshotArgs,
179
      darwinArch: darwinArch,
180
    ));
181
    if (genSnapshotExitCode != 0) {
182
      globals.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode');
183
      return genSnapshotExitCode;
184 185
    }

186
    // On iOS and macOS, we use Xcode to compile the snapshot into a dynamic library that the
187
    // end-developer can link into their app.
188 189 190
    if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) {
      final RunResult result = await _buildFramework(
        appleArch: darwinArch,
191
        isIOS: platform == TargetPlatform.ios,
192
        assemblyPath: assembly,
193 194
        outputPath: outputDir.path,
        bitcode: bitcode,
xster's avatar
xster committed
195
        quiet: quiet,
196
      );
197
      if (result.exitCode != 0) {
198
        return result.exitCode;
199
      }
200 201 202 203
    }
    return 0;
  }

204
  /// Builds an iOS or macOS framework at [outputPath]/App.framework from the assembly
205
  /// source at [assemblyPath].
206 207
  Future<RunResult> _buildFramework({
    @required DarwinArch appleArch,
208
    @required bool isIOS,
209 210
    @required String assemblyPath,
    @required String outputPath,
211
    @required bool bitcode,
xster's avatar
xster committed
212
    @required bool quiet
213
  }) async {
214
    final String targetArch = getNameForDarwinArch(appleArch);
xster's avatar
xster committed
215
    if (!quiet) {
216
      globals.printStatus('Building App.framework for $targetArch...');
xster's avatar
xster committed
217
    }
218

219 220
    final List<String> commonBuildOptions = <String>[
      '-arch', targetArch,
221
      if (isIOS)
222 223
        '-miphoneos-version-min=8.0',
    ];
224

225
    const String embedBitcodeArg = '-fembed-bitcode';
226
    final String assemblyO = globals.fs.path.join(outputPath, 'snapshot_assembly.o');
227 228
    List<String> isysrootArgs;
    if (isIOS) {
229
      final String iPhoneSDKLocation = await globals.xcode.sdkLocation(SdkType.iPhone);
230 231 232 233
      if (iPhoneSDKLocation != null) {
        isysrootArgs = <String>['-isysroot', iPhoneSDKLocation];
      }
    }
234
    final RunResult compileResult = await globals.xcode.cc(<String>[
235
      '-arch', targetArch,
236
      if (isysrootArgs != null) ...isysrootArgs,
237
      if (bitcode) embedBitcodeArg,
238 239 240 241 242
      '-c',
      assemblyPath,
      '-o',
      assemblyO,
    ]);
243
    if (compileResult.exitCode != 0) {
244
      globals.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}');
245
      return compileResult;
246
    }
247

248 249 250
    final String frameworkDir = globals.fs.path.join(outputPath, 'App.framework');
    globals.fs.directory(frameworkDir).createSync(recursive: true);
    final String appLib = globals.fs.path.join(frameworkDir, 'App');
251 252 253 254 255 256
    final List<String> linkArgs = <String>[
      ...commonBuildOptions,
      '-dynamiclib',
      '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
      '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
      '-install_name', '@rpath/App.framework/App',
257
      if (bitcode) embedBitcodeArg,
258
      if (isysrootArgs != null) ...isysrootArgs,
259 260 261
      '-o', appLib,
      assemblyO,
    ];
262
    final RunResult linkResult = await globals.xcode.clang(linkArgs);
263
    if (linkResult.exitCode != 0) {
264
      globals.printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}');
265
    }
266
    return linkResult;
267 268
  }

269 270 271 272 273 274 275
  /// Compiles a Dart file to kernel.
  ///
  /// Returns the output kernel file path, or null on failure.
  Future<String> compileKernel({
    @required TargetPlatform platform,
    @required BuildMode buildMode,
    @required String mainPath,
276
    @required String packagesPath,
277
    @required String outputPath,
278
    @required bool trackWidgetCreation,
279
    @required List<String> dartDefines,
280
    List<String> extraFrontEndOptions = const <String>[],
281
  }) async {
282
    final FlutterProject flutterProject = FlutterProject.current();
283
    final Directory outputDir = globals.fs.directory(outputPath);
284 285
    outputDir.createSync(recursive: true);

286
    globals.printTrace('Compiling Dart to kernel: $mainPath');
287

288
    if ((extraFrontEndOptions != null) && extraFrontEndOptions.isNotEmpty) {
289
      globals.printTrace('Extra front-end options: $extraFrontEndOptions');
290
    }
291

292
    final String depfilePath = globals.fs.path.join(outputPath, 'kernel_compile.d');
293
    final KernelCompiler kernelCompiler = await kernelCompilerFactory.create(flutterProject);
294 295 296
    final CompilerOutput compilerOutput =
      await _timedStep('frontend(CompileTime)', 'aot-kernel',
        () => kernelCompiler.compile(
297
      sdkRoot: globals.artifacts.getArtifactPath(Artifact.flutterPatchedSdkPath, mode: buildMode),
298
      mainPath: mainPath,
299
      packagesPath: packagesPath,
300
      outputFilePath: getKernelPathForTransformerOptions(
301
        globals.fs.path.join(outputPath, 'app.dill'),
302 303
        trackWidgetCreation: trackWidgetCreation,
      ),
304
      depFilePath: depfilePath,
305 306 307
      extraFrontEndOptions: extraFrontEndOptions,
      linkPlatformKernelIn: true,
      aot: true,
308
      buildMode: buildMode,
309
      trackWidgetCreation: trackWidgetCreation,
310
      dartDefines: dartDefines,
311
    ));
312 313

    // Write path to frontend_server, since things need to be re-generated when that changes.
314 315
    final String frontendPath = globals.artifacts.getArtifactPath(Artifact.frontendServerSnapshotForEngineDartSdk);
    globals.fs.directory(outputPath).childFile('frontend_server.d').writeAsStringSync('frontend_server.d: $frontendPath\n');
316 317 318 319

    return compilerOutput?.outputFilename;
  }

320
  bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) {
321
    if (buildMode == BuildMode.debug) {
322
      return false;
323
    }
324 325 326
    return const <TargetPlatform>[
      TargetPlatform.android_arm,
      TargetPlatform.android_arm64,
327
      TargetPlatform.android_x64,
328
      TargetPlatform.ios,
329
      TargetPlatform.darwin_x64,
330 331 332
    ].contains(platform);
  }

333 334 335 336 337
  /// This method is used to measure duration of an action and emit it into
  /// verbose output from flutter_tool for other tools (e.g. benchmark runner)
  /// to find.
  /// Important: external performance tracking tools expect format of this
  /// output to be stable.
338
  Future<T> _timedStep<T>(String marker, String analyticsVar, FutureOr<T> Function() action) async {
339 340 341
    final Stopwatch sw = Stopwatch()..start();
    final T value = await action();
    if (reportTimings) {
342
      globals.printStatus('$marker: ${sw.elapsedMilliseconds} ms.');
343
    }
344
    flutterUsage.sendTiming('build', analyticsVar, Duration(milliseconds: sw.elapsedMilliseconds));
345 346
    return value;
  }
347
}