// 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 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 iosArchs = environment.defines[kIosArchs]?.split(',')?.map(getIOSArchForName)?.toList() ?? [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> pending = >[]; 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 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([ '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 get inputs => const [ 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 get outputs => const [ Source.pattern('{OUTPUT_DIR}/App.framework/App'), ]; @override List get dependencies => const [ 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 get inputs => const [ 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 get outputs => const [ Source.pattern('{OUTPUT_DIR}/App.framework/App'), ]; @override List get dependencies => const [ 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 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 archFlags; if (sdk == SdkType.iPhone) { archFlags = [ '-arch', getNameForDarwinArch(DarwinArch.armv7), '-arch', getNameForDarwinArch(DarwinArch.arm64), ]; } else { archFlags = [ '-arch', getNameForDarwinArch(DarwinArch.x86_64), ]; } return await globals.xcode.clang([ '-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}'); } } }