build.dart 10.4 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 'package:meta/meta.dart';
6
import 'package:process/process.dart';
7

8
import '../artifacts.dart';
9
import '../build_info.dart';
10
import '../macos/xcode.dart';
11

12
import 'file_system.dart';
13
import 'logger.dart';
14 15 16 17
import 'process.dart';

/// A snapshot build configuration.
class SnapshotType {
18 19
  SnapshotType(this.platform, this.mode)
    : assert(mode != null);
20 21 22

  final TargetPlatform platform;
  final BuildMode mode;
23 24 25

  @override
  String toString() => '$platform $mode';
26 27 28 29
}

/// Interface to the gen_snapshot command-line tool.
class GenSnapshot {
30 31 32 33 34 35 36 37 38 39 40 41
  GenSnapshot({
    @required Artifacts artifacts,
    @required ProcessManager processManager,
    @required Logger logger,
  }) : _artifacts = artifacts,
       _processUtils = ProcessUtils(logger: logger, processManager: processManager);

  final Artifacts _artifacts;
  final ProcessUtils _processUtils;

  String getSnapshotterPath(SnapshotType snapshotType) {
    return _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 57
  Future<int> run({
    @required SnapshotType snapshotType,
58
    DarwinArch darwinArch,
59
    Iterable<String> additionalArgs = const <String>[],
60 61
  }) {
    final List<String> args = <String>[
62 63
      ...additionalArgs,
    ];
64

65
    String snapshotterPath = getSnapshotterPath(snapshotType);
66

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

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

80
class AOTSnapshotter {
81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100
  AOTSnapshotter({
    this.reportTimings = false,
    @required Logger logger,
    @required FileSystem fileSystem,
    @required Xcode xcode,
    @required ProcessManager processManager,
    @required Artifacts artifacts,
  }) : _logger = logger,
      _fileSystem = fileSystem,
      _xcode = xcode,
      _genSnapshot = GenSnapshot(
        artifacts: artifacts,
        processManager: processManager,
        logger: logger,
      );

  final Logger _logger;
  final FileSystem _fileSystem;
  final Xcode _xcode;
  final GenSnapshot _genSnapshot;
101 102 103 104 105 106

  /// 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;

107
  /// Builds an architecture-specific ahead-of-time compiled snapshot of the specified script.
108
  Future<int> build({
109 110 111 112
    @required TargetPlatform platform,
    @required BuildMode buildMode,
    @required String mainPath,
    @required String outputPath,
113
    DarwinArch darwinArch,
114
    List<String> extraGenSnapshotOptions = const <String>[],
115
    @required bool bitcode,
116
    @required String splitDebugInfo,
117
    @required bool dartObfuscation,
xster's avatar
xster committed
118
    bool quiet = false,
119
  }) async {
120 121
    // TODO(cbracken): replace IOSArch with TargetPlatform.ios_{armv7,arm64}.
    assert(platform != TargetPlatform.ios || darwinArch != null);
122
    if (bitcode && platform != TargetPlatform.ios) {
123
      _logger.printError('Bitcode is only supported for iOS.');
124 125 126
      return 1;
    }

127
    if (!_isValidAotPlatform(platform, buildMode)) {
128
      _logger.printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
129
      return 1;
130 131
    }

132
    final Directory outputDir = _fileSystem.directory(outputPath);
133 134
    outputDir.createSync(recursive: true);

135
    final List<String> genSnapshotArgs = <String>[
136
      '--deterministic',
137
    ];
138
    if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) {
139
      _logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions');
140 141 142
      genSnapshotArgs.addAll(extraGenSnapshotOptions);
    }

143
    final String assembly = _fileSystem.path.join(outputDir.path, 'snapshot_assembly.S');
144
    if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) {
145 146 147 148 149
      genSnapshotArgs.addAll(<String>[
        '--snapshot_kind=app-aot-assembly',
        '--assembly=$assembly',
        '--strip'
      ]);
150
    } else {
151 152 153 154 155 156
      final String aotSharedLibrary = _fileSystem.path.join(outputDir.path, 'app.so');
      genSnapshotArgs.addAll(<String>[
        '--snapshot_kind=app-aot-elf',
        '--elf=$aotSharedLibrary',
        '--strip'
      ]);
157 158
    }

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

165 166
      // Not supported by the Pixel in 32-bit mode.
      genSnapshotArgs.add('--no-use-integer-division');
167 168
    }

169
    // The name of the debug file must contain additional information about
170 171 172 173
    // 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';
174 175
    final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false;
    if (shouldSplitDebugInfo) {
176
      _fileSystem.directory(splitDebugInfo)
177 178 179
        .createSync(recursive: true);
    }

180 181 182 183 184
    // Optimization arguments.
    genSnapshotArgs.addAll(<String>[
      // Faster async/await
      '--no-causal-async-stacks',
      '--lazy-async-stacks',
185
      if (shouldSplitDebugInfo) ...<String>[
186
        '--dwarf-stack-traces',
187
        '--save-debugging-info=${_fileSystem.path.join(splitDebugInfo, debugFilename)}'
188 189 190
      ],
      if (dartObfuscation)
        '--obfuscate',
191 192
    ]);

193
    genSnapshotArgs.add(mainPath);
194

195
    final SnapshotType snapshotType = SnapshotType(platform, buildMode);
196
    final int genSnapshotExitCode = await _genSnapshot.run(
197
      snapshotType: snapshotType,
198
      additionalArgs: genSnapshotArgs,
199
      darwinArch: darwinArch,
200
    );
201
    if (genSnapshotExitCode != 0) {
202
      _logger.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode');
203
      return genSnapshotExitCode;
204 205
    }

206
    // On iOS and macOS, we use Xcode to compile the snapshot into a dynamic library that the
207
    // end-developer can link into their app.
208
    if (platform == TargetPlatform.ios || platform == TargetPlatform.darwin_x64) {
209 210
      final RunResult result = await _buildFramework(
        appleArch: darwinArch,
211
        isIOS: platform == TargetPlatform.ios,
212
        assemblyPath: assembly,
213 214
        outputPath: outputDir.path,
        bitcode: bitcode,
xster's avatar
xster committed
215
        quiet: quiet,
216
      );
217
      if (result.exitCode != 0) {
218
        return result.exitCode;
219
      }
220 221 222 223
    }
    return 0;
  }

224
  /// Builds an iOS or macOS framework at [outputPath]/App.framework from the assembly
225
  /// source at [assemblyPath].
226 227
  Future<RunResult> _buildFramework({
    @required DarwinArch appleArch,
228
    @required bool isIOS,
229 230
    @required String assemblyPath,
    @required String outputPath,
231
    @required bool bitcode,
xster's avatar
xster committed
232
    @required bool quiet
233
  }) async {
234
    final String targetArch = getNameForDarwinArch(appleArch);
xster's avatar
xster committed
235
    if (!quiet) {
236
      _logger.printStatus('Building App.framework for $targetArch...');
xster's avatar
xster committed
237
    }
238

239 240
    final List<String> commonBuildOptions = <String>[
      '-arch', targetArch,
241
      if (isIOS)
242
        // When the minimum version is updated, remember to update
243
        // template MinimumOSVersion.
244
        // https://github.com/flutter/flutter/pull/62902
245
        '-miphoneos-version-min=8.0',
246
    ];
247

248
    const String embedBitcodeArg = '-fembed-bitcode';
249
    final String assemblyO = _fileSystem.path.join(outputPath, 'snapshot_assembly.o');
250 251
    List<String> isysrootArgs;
    if (isIOS) {
252
      final String iPhoneSDKLocation = await _xcode.sdkLocation(SdkType.iPhone);
253 254 255 256
      if (iPhoneSDKLocation != null) {
        isysrootArgs = <String>['-isysroot', iPhoneSDKLocation];
      }
    }
257
    final RunResult compileResult = await _xcode.cc(<String>[
258
      '-arch', targetArch,
259
      if (isysrootArgs != null) ...isysrootArgs,
260
      if (bitcode) embedBitcodeArg,
261 262 263 264 265
      '-c',
      assemblyPath,
      '-o',
      assemblyO,
    ]);
266
    if (compileResult.exitCode != 0) {
267
      _logger.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}');
268
      return compileResult;
269
    }
270

271 272 273
    final String frameworkDir = _fileSystem.path.join(outputPath, 'App.framework');
    _fileSystem.directory(frameworkDir).createSync(recursive: true);
    final String appLib = _fileSystem.path.join(frameworkDir, 'App');
274 275 276 277 278 279
    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',
280
      if (bitcode) embedBitcodeArg,
281
      if (isysrootArgs != null) ...isysrootArgs,
282 283 284
      '-o', appLib,
      assemblyO,
    ];
285
    final RunResult linkResult = await _xcode.clang(linkArgs);
286
    if (linkResult.exitCode != 0) {
287
      _logger.printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}');
288
    }
289
    return linkResult;
290 291
  }

292
  bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) {
293
    if (buildMode == BuildMode.debug) {
294
      return false;
295
    }
296 297 298
    return const <TargetPlatform>[
      TargetPlatform.android_arm,
      TargetPlatform.android_arm64,
299
      TargetPlatform.android_x64,
300
      TargetPlatform.ios,
301 302 303
      TargetPlatform.darwin_x64,
      TargetPlatform.linux_x64,
      TargetPlatform.windows_x64,
304 305
    ].contains(platform);
  }
306
}