| // Copyright 2014 The Flutter 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:process/process.dart'; |
| |
| import '../artifacts.dart'; |
| import '../build_info.dart'; |
| import '../macos/xcode.dart'; |
| |
| import 'file_system.dart'; |
| import 'logger.dart'; |
| import 'process.dart'; |
| |
| /// A snapshot build configuration. |
| class SnapshotType { |
| SnapshotType(this.platform, this.mode) |
| : assert(mode != null); |
| |
| final TargetPlatform? platform; |
| final BuildMode mode; |
| |
| @override |
| String toString() => '$platform $mode'; |
| } |
| |
| /// Interface to the gen_snapshot command-line tool. |
| class GenSnapshot { |
| 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( |
| Artifact.genSnapshot, platform: snapshotType.platform, mode: snapshotType.mode); |
| } |
| |
| /// 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.', |
| }; |
| |
| Future<int> run({ |
| required SnapshotType snapshotType, |
| DarwinArch? darwinArch, |
| Iterable<String> additionalArgs = const <String>[], |
| }) { |
| assert(darwinArch != DarwinArch.armv7); |
| assert(snapshotType.platform != TargetPlatform.ios || darwinArch != null); |
| final List<String> args = <String>[ |
| ...additionalArgs, |
| ]; |
| |
| String snapshotterPath = getSnapshotterPath(snapshotType); |
| |
| // iOS and macOS have separate gen_snapshot binaries for each target |
| // architecture (iOS: armv7, arm64; macOS: x86_64, arm64). Select the right |
| // one for the target architecture in question. |
| if (snapshotType.platform == TargetPlatform.ios || |
| snapshotType.platform == TargetPlatform.darwin) { |
| snapshotterPath += '_${getDartNameForDarwinArch(darwinArch!)}'; |
| } |
| |
| return _processUtils.stream( |
| <String>[snapshotterPath, ...args], |
| mapFunction: (String line) => kIgnoredWarnings.contains(line) ? null : line, |
| ); |
| } |
| } |
| |
| class AOTSnapshotter { |
| 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; |
| |
| /// 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; |
| |
| /// Builds an architecture-specific ahead-of-time compiled snapshot of the specified script. |
| Future<int> build({ |
| required TargetPlatform platform, |
| required BuildMode buildMode, |
| required String mainPath, |
| required String outputPath, |
| DarwinArch? darwinArch, |
| String? sdkRoot, |
| List<String> extraGenSnapshotOptions = const <String>[], |
| String? splitDebugInfo, |
| required bool dartObfuscation, |
| bool quiet = false, |
| }) async { |
| assert(platform != TargetPlatform.ios || darwinArch != null); |
| |
| if (!_isValidAotPlatform(platform, buildMode)) { |
| _logger.printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.'); |
| return 1; |
| } |
| |
| final Directory outputDir = _fileSystem.directory(outputPath); |
| outputDir.createSync(recursive: true); |
| |
| final List<String> genSnapshotArgs = <String>[ |
| '--deterministic', |
| ]; |
| |
| final bool targetingApplePlatform = |
| platform == TargetPlatform.ios || platform == TargetPlatform.darwin; |
| _logger.printTrace('targetingApplePlatform = $targetingApplePlatform'); |
| |
| final bool extractAppleDebugSymbols = |
| buildMode == BuildMode.profile || buildMode == BuildMode.release; |
| _logger.printTrace('extractAppleDebugSymbols = $extractAppleDebugSymbols'); |
| |
| // We strip snapshot by default, but allow to suppress this behavior |
| // by supplying --no-strip in extraGenSnapshotOptions. |
| bool shouldStrip = true; |
| if (extraGenSnapshotOptions != null && extraGenSnapshotOptions.isNotEmpty) { |
| _logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions'); |
| for (final String option in extraGenSnapshotOptions) { |
| if (option == '--no-strip') { |
| shouldStrip = false; |
| continue; |
| } |
| genSnapshotArgs.add(option); |
| } |
| } |
| |
| final String assembly = _fileSystem.path.join(outputDir.path, 'snapshot_assembly.S'); |
| if (targetingApplePlatform) { |
| genSnapshotArgs.addAll(<String>[ |
| '--snapshot_kind=app-aot-assembly', |
| '--assembly=$assembly', |
| ]); |
| } else { |
| final String aotSharedLibrary = _fileSystem.path.join(outputDir.path, 'app.so'); |
| genSnapshotArgs.addAll(<String>[ |
| '--snapshot_kind=app-aot-elf', |
| '--elf=$aotSharedLibrary', |
| ]); |
| } |
| |
| // When buiding for iOS and splitting out debug info, we want to strip |
| // manually after the dSYM export, instead of in the `gen_snapshot`. |
| final bool stripAfterBuild; |
| if (targetingApplePlatform) { |
| stripAfterBuild = shouldStrip; |
| if (stripAfterBuild) { |
| _logger.printTrace('Will strip AOT snapshot manually after build and dSYM generation.'); |
| } |
| } else { |
| stripAfterBuild = false; |
| if (shouldStrip) { |
| genSnapshotArgs.add('--strip'); |
| _logger.printTrace('Will strip AOT snapshot during build.'); |
| } |
| } |
| |
| if (platform == TargetPlatform.android_arm) { |
| // Use softfp for Android armv7 devices. |
| // TODO(cbracken): eliminate this when we fix https://github.com/flutter/flutter/issues/17489 |
| genSnapshotArgs.add('--no-sim-use-hardfp'); |
| |
| // Not supported by the Pixel in 32-bit mode. |
| genSnapshotArgs.add('--no-use-integer-division'); |
| } |
| |
| // The name of the debug file must contain additional information about |
| // 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'; |
| final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false; |
| if (shouldSplitDebugInfo) { |
| _fileSystem.directory(splitDebugInfo) |
| .createSync(recursive: true); |
| } |
| |
| // Optimization arguments. |
| genSnapshotArgs.addAll(<String>[ |
| // Faster async/await |
| if (shouldSplitDebugInfo) ...<String>[ |
| '--dwarf-stack-traces', |
| '--save-debugging-info=${_fileSystem.path.join(splitDebugInfo!, debugFilename)}', |
| ], |
| if (dartObfuscation) |
| '--obfuscate', |
| ]); |
| |
| genSnapshotArgs.add(mainPath); |
| |
| final SnapshotType snapshotType = SnapshotType(platform, buildMode); |
| final int genSnapshotExitCode = await _genSnapshot.run( |
| snapshotType: snapshotType, |
| additionalArgs: genSnapshotArgs, |
| darwinArch: darwinArch, |
| ); |
| if (genSnapshotExitCode != 0) { |
| _logger.printError('Dart snapshot generator failed with exit code $genSnapshotExitCode'); |
| return genSnapshotExitCode; |
| } |
| |
| // On iOS and macOS, we use Xcode to compile the snapshot into a dynamic library that the |
| // end-developer can link into their app. |
| if (targetingApplePlatform) { |
| return _buildFramework( |
| appleArch: darwinArch!, |
| isIOS: platform == TargetPlatform.ios, |
| sdkRoot: sdkRoot, |
| assemblyPath: assembly, |
| outputPath: outputDir.path, |
| quiet: quiet, |
| stripAfterBuild: stripAfterBuild, |
| extractAppleDebugSymbols: extractAppleDebugSymbols |
| ); |
| } else { |
| return 0; |
| } |
| } |
| |
| /// Builds an iOS or macOS framework at [outputPath]/App.framework from the assembly |
| /// source at [assemblyPath]. |
| Future<int> _buildFramework({ |
| required DarwinArch appleArch, |
| required bool isIOS, |
| String? sdkRoot, |
| required String assemblyPath, |
| required String outputPath, |
| required bool quiet, |
| required bool stripAfterBuild, |
| required bool extractAppleDebugSymbols |
| }) async { |
| final String targetArch = getNameForDarwinArch(appleArch); |
| if (!quiet) { |
| _logger.printStatus('Building App.framework for $targetArch...'); |
| } |
| |
| final List<String> commonBuildOptions = <String>[ |
| '-arch', |
| targetArch, |
| if (isIOS) |
| // When the minimum version is updated, remember to update |
| // template MinimumOSVersion. |
| // https://github.com/flutter/flutter/pull/62902 |
| '-miphoneos-version-min=11.0', |
| if (sdkRoot != null) ...<String>[ |
| '-isysroot', |
| sdkRoot, |
| ], |
| ]; |
| |
| final String assemblyO = _fileSystem.path.join(outputPath, 'snapshot_assembly.o'); |
| |
| final RunResult compileResult = await _xcode.cc(<String>[ |
| ...commonBuildOptions, |
| '-c', |
| assemblyPath, |
| '-o', |
| assemblyO, |
| ]); |
| if (compileResult.exitCode != 0) { |
| _logger.printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}'); |
| return compileResult.exitCode; |
| } |
| |
| final String frameworkDir = _fileSystem.path.join(outputPath, 'App.framework'); |
| _fileSystem.directory(frameworkDir).createSync(recursive: true); |
| final String appLib = _fileSystem.path.join(frameworkDir, 'App'); |
| 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', |
| '-o', appLib, |
| assemblyO, |
| ]; |
| |
| final RunResult linkResult = await _xcode.clang(linkArgs); |
| if (linkResult.exitCode != 0) { |
| _logger.printError('Failed to link AOT snapshot. Linker terminated with exit code ${linkResult.exitCode}'); |
| return linkResult.exitCode; |
| } |
| |
| if (extractAppleDebugSymbols) { |
| final RunResult dsymResult = await _xcode.dsymutil(<String>['-o', '$frameworkDir.dSYM', appLib]); |
| if (dsymResult.exitCode != 0) { |
| _logger.printError('Failed to generate dSYM - dsymutil terminated with exit code ${dsymResult.exitCode}'); |
| return dsymResult.exitCode; |
| } |
| |
| if (stripAfterBuild) { |
| // See https://www.unix.com/man-page/osx/1/strip/ for arguments |
| final RunResult stripResult = await _xcode.strip(<String>['-x', appLib, '-o', appLib]); |
| if (stripResult.exitCode != 0) { |
| _logger.printError('Failed to strip debugging symbols from the generated AOT snapshot - strip terminated with exit code ${stripResult.exitCode}'); |
| return stripResult.exitCode; |
| } |
| } |
| } else { |
| assert(stripAfterBuild == false); |
| } |
| |
| return 0; |
| } |
| |
| bool _isValidAotPlatform(TargetPlatform platform, BuildMode buildMode) { |
| if (buildMode == BuildMode.debug) { |
| return false; |
| } |
| return const <TargetPlatform>[ |
| TargetPlatform.android_arm, |
| TargetPlatform.android_arm64, |
| TargetPlatform.android_x64, |
| TargetPlatform.ios, |
| TargetPlatform.darwin, |
| TargetPlatform.linux_x64, |
| TargetPlatform.linux_arm64, |
| TargetPlatform.windows_x64, |
| ].contains(platform); |
| } |
| } |