| // 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 '../../artifacts.dart'; |
| import '../../base/build.dart'; |
| import '../../base/common.dart'; |
| import '../../base/file_system.dart'; |
| import '../../base/io.dart'; |
| import '../../base/process.dart'; |
| import '../../build_info.dart'; |
| import '../../globals.dart' as globals; |
| import '../../macos/xcode.dart'; |
| import '../build_system.dart'; |
| import '../exceptions.dart'; |
| import 'dart.dart'; |
| |
| /// Supports compiling a dart kernel file to an assembly file. |
| /// |
| /// If more than one iOS arch is provided, then this rule will |
| /// produce a universal binary. |
| abstract class AotAssemblyBase extends Target { |
| const AotAssemblyBase(); |
| |
| @override |
| Future<void> build(Environment environment) async { |
| final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false); |
| final String buildOutputPath = environment.outputDir.path; |
| if (environment.defines[kBuildMode] == null) { |
| throw MissingDefineException(kBuildMode, 'aot_assembly'); |
| } |
| if (environment.defines[kTargetPlatform] == null) { |
| throw MissingDefineException(kTargetPlatform, 'aot_assembly'); |
| } |
| final bool bitcode = environment.defines[kBitcodeFlag] == 'true'; |
| final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]); |
| final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]); |
| final List<DarwinArch> iosArchs = environment.defines[kIosArchs]?.split(',')?.map(getIOSArchForName)?.toList() |
| ?? <DarwinArch>[DarwinArch.arm64]; |
| if (targetPlatform != TargetPlatform.ios) { |
| throw Exception('aot_assembly is only supported for iOS applications'); |
| } |
| |
| // If we're building multiple iOS archs the binaries need to be lipo'd |
| // together. |
| final List<Future<int>> pending = <Future<int>>[]; |
| for (final DarwinArch iosArch in iosArchs) { |
| pending.add(snapshotter.build( |
| platform: targetPlatform, |
| buildMode: buildMode, |
| mainPath: environment.buildDir.childFile('app.dill').path, |
| packagesPath: environment.projectDir.childFile('.packages').path, |
| outputPath: globals.fs.path.join(buildOutputPath, getNameForDarwinArch(iosArch)), |
| darwinArch: iosArch, |
| bitcode: bitcode, |
| quiet: true, |
| )); |
| } |
| final List<int> results = await Future.wait(pending); |
| if (results.any((int result) => result != 0)) { |
| throw Exception('AOT snapshotter exited with code ${results.join()}'); |
| } |
| final String resultPath = globals.fs.path.join(environment.outputDir.path, 'App.framework', 'App'); |
| globals.fs.directory(resultPath).parent.createSync(recursive: true); |
| final ProcessResult result = await globals.processManager.run(<String>[ |
| 'lipo', |
| ...iosArchs.map((DarwinArch iosArch) => |
| globals.fs.path.join(buildOutputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')), |
| '-create', |
| '-output', |
| resultPath, |
| ]); |
| if (result.exitCode != 0) { |
| throw Exception('lipo exited with code ${result.exitCode}.\n${result.stderr}'); |
| } |
| } |
| } |
| |
| /// Generate an assembly target from a dart kernel file in release mode. |
| class AotAssemblyRelease extends AotAssemblyBase { |
| const AotAssemblyRelease(); |
| |
| @override |
| String get name => 'aot_assembly_release'; |
| |
| @override |
| List<Source> get inputs => const <Source>[ |
| Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'), |
| Source.pattern('{BUILD_DIR}/app.dill'), |
| Source.pattern('{PROJECT_DIR}/.packages'), |
| Source.artifact(Artifact.engineDartBinary), |
| Source.artifact(Artifact.skyEnginePath), |
| // TODO(jonahwilliams): cannot reference gen_snapshot with artifacts since |
| // it resolves to a file (ios/gen_snapshot) that never exists. This was |
| // split into gen_snapshot_arm64 and gen_snapshot_armv7. |
| // Source.artifact(Artifact.genSnapshot, |
| // platform: TargetPlatform.ios, |
| // mode: BuildMode.release, |
| // ), |
| ]; |
| |
| @override |
| List<Source> get outputs => const <Source>[ |
| Source.pattern('{OUTPUT_DIR}/App.framework/App'), |
| ]; |
| |
| @override |
| List<Target> get dependencies => const <Target>[ |
| KernelSnapshot(), |
| ]; |
| } |
| |
| |
| /// Generate an assembly target from a dart kernel file in profile mode. |
| class AotAssemblyProfile extends AotAssemblyBase { |
| const AotAssemblyProfile(); |
| |
| @override |
| String get name => 'aot_assembly_profile'; |
| |
| @override |
| List<Source> get inputs => const <Source>[ |
| Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'), |
| Source.pattern('{BUILD_DIR}/app.dill'), |
| Source.pattern('{PROJECT_DIR}/.packages'), |
| Source.artifact(Artifact.engineDartBinary), |
| Source.artifact(Artifact.skyEnginePath), |
| // TODO(jonahwilliams): cannot reference gen_snapshot with artifacts since |
| // it resolves to a file (ios/gen_snapshot) that never exists. This was |
| // split into gen_snapshot_arm64 and gen_snapshot_armv7. |
| // Source.artifact(Artifact.genSnapshot, |
| // platform: TargetPlatform.ios, |
| // mode: BuildMode.profile, |
| // ), |
| ]; |
| |
| @override |
| List<Source> get outputs => const <Source>[ |
| Source.pattern('{OUTPUT_DIR}/App.framework/App'), |
| ]; |
| |
| @override |
| List<Target> get dependencies => const <Target>[ |
| KernelSnapshot(), |
| ]; |
| } |
| |
| /// Create an App.framework for debug iOS targets. |
| /// |
| /// This framework needs to exist for the Xcode project to link/bundle, |
| /// but it isn't actually executed. To generate something valid, we compile a trivial |
| /// constant. |
| Future<RunResult> createStubAppFramework(File outputFile, SdkType sdk) async { |
| try { |
| outputFile.createSync(recursive: true); |
| } catch (e) { |
| throwToolExit('Failed to create App.framework stub at ${outputFile.path}'); |
| } |
| |
| final Directory tempDir = globals.fs.systemTempDirectory.createTempSync('flutter_tools_stub_source.'); |
| try { |
| final File stubSource = tempDir.childFile('debug_app.cc') |
| ..writeAsStringSync(r''' |
| static const int Moo = 88; |
| '''); |
| |
| List<String> archFlags; |
| if (sdk == SdkType.iPhone) { |
| archFlags = <String>[ |
| '-arch', |
| getNameForDarwinArch(DarwinArch.armv7), |
| '-arch', |
| getNameForDarwinArch(DarwinArch.arm64), |
| ]; |
| } else { |
| archFlags = <String>[ |
| '-arch', |
| getNameForDarwinArch(DarwinArch.x86_64), |
| ]; |
| } |
| |
| return await globals.xcode.clang(<String>[ |
| '-x', |
| 'c', |
| ...archFlags, |
| stubSource.path, |
| '-dynamiclib', |
| '-fembed-bitcode-marker', |
| '-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks', |
| '-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks', |
| '-install_name', '@rpath/App.framework/App', |
| '-isysroot', await globals.xcode.sdkLocation(sdk), |
| '-o', outputFile.path, |
| ]); |
| } finally { |
| try { |
| tempDir.deleteSync(recursive: true); |
| } on FileSystemException catch (_) { |
| // Best effort. Sometimes we can't delete things from system temp. |
| } catch (e) { |
| throwToolExit('Failed to create App.framework stub at ${outputFile.path}'); |
| } |
| } |
| } |