| // 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 '../darwin/darwin.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); |
| |
| 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, Artifact artifact) { |
| return _artifacts.getArtifactPath( |
| artifact, |
| platform: snapshotType.platform, |
| mode: snapshotType.mode, |
| ); |
| } |
| |
| /// Ignored warning messages from gen_snapshot. |
| static const 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 args = <String>[...additionalArgs]; |
| |
| // 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. |
| Artifact genSnapshotArtifact; |
| if (snapshotType.platform == TargetPlatform.ios || |
| snapshotType.platform == TargetPlatform.darwin) { |
| genSnapshotArtifact = darwinArch == DarwinArch.arm64 |
| ? Artifact.genSnapshotArm64 |
| : Artifact.genSnapshotX64; |
| } else { |
| genSnapshotArtifact = Artifact.genSnapshot; |
| } |
| |
| final String snapshotterPath = getSnapshotterPath(snapshotType, genSnapshotArtifact); |
| |
| return _processUtils.stream(<String>[ |
| snapshotterPath, |
| ...args, |
| ], mapFunction: (String line) => kIgnoredWarnings.contains(line) ? null : line); |
| } |
| } |
| |
| class AOTSnapshotter { |
| AOTSnapshotter({ |
| 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; |
| |
| /// 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 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. |
| var shouldStrip = true; |
| if (extraGenSnapshotOptions.isNotEmpty) { |
| _logger.printTrace('Extra gen_snapshot options: $extraGenSnapshotOptions'); |
| for (final option in extraGenSnapshotOptions) { |
| if (option == '--no-strip') { |
| shouldStrip = false; |
| continue; |
| } |
| genSnapshotArgs.add(option); |
| } |
| } |
| |
| String aotSharedLibrary = _fileSystem.path.join(outputDir.path, 'app.so'); |
| String? frameworkPath; |
| if (targetingApplePlatform) { |
| // On iOS and macOS, we use Xcode to compile the snapshot into a dynamic |
| // library that the end-developer can link into their app. |
| const frameworkName = 'App.framework'; |
| if (!quiet) { |
| final String targetArch = darwinArch!.name; |
| _logger.printStatus('Building $frameworkName for $targetArch...'); |
| } |
| frameworkPath = _fileSystem.path.join(outputPath, frameworkName); |
| _fileSystem.directory(frameworkPath).createSync(recursive: true); |
| |
| const frameworkSnapshotName = 'App'; |
| aotSharedLibrary = _fileSystem.path.join(frameworkPath, frameworkSnapshotName); |
| final String relocatableObject = _fileSystem.path.join(outputPath, 'app.o'); |
| // When the minimum version is updated, remember to update |
| // template MinimumOSVersion. |
| // https://github.com/flutter/flutter/pull/62902 |
| final minOSVersion = platform == TargetPlatform.ios |
| ? FlutterDarwinPlatform.ios.deploymentTarget().toString() |
| : FlutterDarwinPlatform.macos.deploymentTarget().toString(); |
| genSnapshotArgs.addAll(<String>[ |
| '--snapshot_kind=app-aot-macho-dylib', |
| '--macho=$aotSharedLibrary', |
| '--macho-object=$relocatableObject', |
| '--macho-min-os-version=$minOSVersion', |
| '--macho-rpath=@executable_path/Frameworks,@loader_path/Frameworks', |
| '--macho-install-name=@rpath/$frameworkName/$frameworkSnapshotName', |
| ]); |
| } else { |
| genSnapshotArgs.addAll(<String>['--snapshot_kind=app-aot-elf', '--elf=$aotSharedLibrary']); |
| } |
| |
| // When building 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 debugFilename = 'app.$archName.symbols'; |
| final bool shouldSplitDebugInfo = splitDebugInfo?.isNotEmpty ?? false; |
| if (shouldSplitDebugInfo) { |
| _fileSystem.directory(splitDebugInfo).createSync(recursive: true); |
| } |
| |
| // Debugging information. |
| genSnapshotArgs.addAll(<String>[ |
| if (shouldSplitDebugInfo) ...<String>[ |
| '--dwarf-stack-traces', |
| '--resolve-dwarf-paths', |
| '--save-debugging-info=${_fileSystem.path.join(splitDebugInfo!, debugFilename)}', |
| ], |
| if (dartObfuscation) '--obfuscate', |
| ]); |
| |
| genSnapshotArgs.add(mainPath); |
| |
| final 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; |
| } |
| |
| if (targetingApplePlatform) { |
| if (extractAppleDebugSymbols) { |
| final RunResult dsymResult = await _xcode.dsymutil(<String>[ |
| '-o', |
| '$frameworkPath.dSYM', |
| aotSharedLibrary, |
| ]); |
| 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', |
| aotSharedLibrary, |
| '-o', |
| aotSharedLibrary, |
| ]); |
| 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); |
| } |
| } |
| |
| 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, |
| TargetPlatform.windows_arm64, |
| ].contains(platform); |
| } |
| } |