mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
426 lines
14 KiB
Dart
426 lines
14 KiB
Dart
// 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 '../../project.dart';
|
|
import '../build_system.dart';
|
|
import '../depfile.dart';
|
|
import '../exceptions.dart';
|
|
import 'assets.dart';
|
|
import 'dart.dart';
|
|
import 'icon_tree_shaker.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.buildDir.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 String splitDebugInfo = environment.defines[kSplitDebugInfo];
|
|
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,
|
|
splitDebugInfo: splitDebugInfo,
|
|
));
|
|
}
|
|
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.buildDir.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 a trivial App.framework file for debug iOS builds.
|
|
class DebugUniveralFramework extends Target {
|
|
const DebugUniveralFramework();
|
|
|
|
@override
|
|
String get name => 'debug_universal_framework';
|
|
|
|
@override
|
|
List<Target> get dependencies => const <Target>[
|
|
KernelSnapshot(),
|
|
];
|
|
|
|
@override
|
|
List<Source> get inputs => const <Source>[
|
|
Source.pattern('{FLUTTER_ROOT}/packages/flutter_tools/lib/src/build_system/targets/ios.dart'),
|
|
];
|
|
|
|
@override
|
|
List<Source> get outputs => const <Source>[
|
|
Source.pattern('{BUILD_DIR}/App')
|
|
];
|
|
|
|
@override
|
|
Future<void> build(Environment environment) async {
|
|
// Generate a trivial App.framework.
|
|
final Set<DarwinArch> iosArchs = environment.defines[kIosArchs]
|
|
?.split(' ')
|
|
?.map(getIOSArchForName)
|
|
?.toSet()
|
|
?? <DarwinArch>{DarwinArch.arm64};
|
|
final File iphoneFile = environment.buildDir.childFile('iphone_framework');
|
|
final File simulatorFile = environment.buildDir.childFile('simulator_framework');
|
|
final File lipoOutputFile = environment.buildDir.childFile('App');
|
|
final RunResult iphoneResult = await createStubAppFramework(
|
|
iphoneFile,
|
|
SdkType.iPhone,
|
|
// Only include 32bit if it is contained in the active architectures.
|
|
include32Bit: iosArchs.contains(DarwinArch.armv7)
|
|
);
|
|
final RunResult simulatorResult = await createStubAppFramework(
|
|
simulatorFile,
|
|
SdkType.iPhoneSimulator,
|
|
);
|
|
if (iphoneResult.exitCode != 0 || simulatorResult.exitCode != 0) {
|
|
throw Exception('Failed to create App.framework.');
|
|
}
|
|
final List<String> lipoCommand = <String>[
|
|
'xcrun',
|
|
'lipo',
|
|
'-create',
|
|
iphoneFile.path,
|
|
simulatorFile.path,
|
|
'-output',
|
|
lipoOutputFile.path
|
|
];
|
|
final RunResult lipoResult = await processUtils.run(
|
|
lipoCommand,
|
|
);
|
|
|
|
if (lipoResult.exitCode != 0) {
|
|
throw Exception('Failed to create App.framework.');
|
|
}
|
|
}
|
|
}
|
|
|
|
/// The base class for all iOS bundle targets.
|
|
///
|
|
/// This is responsible for setting up the basic App.framework structure, including:
|
|
/// * Copying the app.dill/kernel_blob.bin from the build directory to assets (debug)
|
|
/// * Copying the precompiled isolate/vm data from the engine (debug)
|
|
/// * Copying the flutter assets to App.framework/flutter_assets
|
|
/// * Copying either the stub or real App assembly file to App.framework/App
|
|
abstract class IosAssetBundle extends Target {
|
|
const IosAssetBundle();
|
|
|
|
@override
|
|
List<Target> get dependencies => const <Target>[
|
|
KernelSnapshot(),
|
|
];
|
|
|
|
@override
|
|
List<Source> get inputs => const <Source>[
|
|
Source.pattern('{BUILD_DIR}/App'),
|
|
Source.pattern('{PROJECT_DIR}/pubspec.yaml'),
|
|
...IconTreeShaker.inputs,
|
|
];
|
|
|
|
@override
|
|
List<Source> get outputs => const <Source>[
|
|
Source.pattern('{OUTPUT_DIR}/App.framework/App'),
|
|
Source.pattern('{OUTPUT_DIR}/App.framework/Info.plist')
|
|
];
|
|
|
|
@override
|
|
List<String> get depfiles => <String>[
|
|
'flutter_assets.d',
|
|
];
|
|
|
|
@override
|
|
Future<void> build(Environment environment) async {
|
|
if (environment.defines[kBuildMode] == null) {
|
|
throw MissingDefineException(kBuildMode, name);
|
|
}
|
|
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
|
final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
|
|
final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
|
|
frameworkDirectory.createSync(recursive: true);
|
|
assetDirectory.createSync();
|
|
|
|
// Only copy the prebuilt runtimes and kernel blob in debug mode.
|
|
if (buildMode == BuildMode.debug) {
|
|
// Copy the App.framework to the output directory.
|
|
environment.buildDir.childFile('App')
|
|
.copySync(frameworkDirectory.childFile('App').path);
|
|
|
|
final String vmSnapshotData = globals.artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug);
|
|
final String isolateSnapshotData = globals.artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug);
|
|
environment.buildDir.childFile('app.dill')
|
|
.copySync(assetDirectory.childFile('kernel_blob.bin').path);
|
|
globals.fs.file(vmSnapshotData)
|
|
.copySync(assetDirectory.childFile('vm_snapshot_data').path);
|
|
globals.fs.file(isolateSnapshotData)
|
|
.copySync(assetDirectory.childFile('isolate_snapshot_data').path);
|
|
} else {
|
|
environment.buildDir.childDirectory('App.framework').childFile('App')
|
|
.copySync(frameworkDirectory.childFile('App').path);
|
|
}
|
|
|
|
// Copy the assets.
|
|
final Depfile assetDepfile = await copyAssets(environment, assetDirectory);
|
|
final DepfileService depfileService = DepfileService(
|
|
fileSystem: globals.fs,
|
|
logger: globals.logger,
|
|
platform: globals.platform,
|
|
);
|
|
depfileService.writeToFile(
|
|
assetDepfile,
|
|
environment.buildDir.childFile('flutter_assets.d'),
|
|
);
|
|
|
|
|
|
// Copy the plist from either the project or module.
|
|
// TODO(jonahwilliams): add plist to inputs
|
|
final FlutterProject flutterProject = FlutterProject.fromDirectory(environment.projectDir);
|
|
final Directory plistRoot = flutterProject.isModule
|
|
? flutterProject.ios.ephemeralDirectory
|
|
: environment.projectDir.childDirectory('ios');
|
|
plistRoot
|
|
.childDirectory('Flutter')
|
|
.childFile('AppFrameworkInfo.plist')
|
|
.copySync(environment.outputDir
|
|
.childDirectory('App.framework')
|
|
.childFile('Info.plist').path);
|
|
}
|
|
}
|
|
|
|
/// Build a debug iOS application bundle.
|
|
class DebugIosApplicationBundle extends IosAssetBundle {
|
|
const DebugIosApplicationBundle();
|
|
|
|
@override
|
|
String get name => 'debug_ios_bundle_flutter_assets';
|
|
|
|
@override
|
|
List<Source> get inputs => <Source>[
|
|
const Source.artifact(Artifact.vmSnapshotData, mode: BuildMode.debug),
|
|
const Source.artifact(Artifact.isolateSnapshotData, mode: BuildMode.debug),
|
|
const Source.pattern('{BUILD_DIR}/app.dill'),
|
|
...super.inputs,
|
|
];
|
|
|
|
@override
|
|
List<Source> get outputs => <Source>[
|
|
const Source.pattern('{OUTPUT_DIR}/App.framework/flutter_assets/vm_snapshot_data'),
|
|
const Source.pattern('{OUTPUT_DIR}/App.framework/flutter_assets/isolate_snapshot_data'),
|
|
const Source.pattern('{OUTPUT_DIR}/App.framework/flutter_assets/kernel_blob.bin'),
|
|
...super.outputs,
|
|
];
|
|
|
|
@override
|
|
List<Target> get dependencies => <Target>[
|
|
const DebugUniveralFramework(),
|
|
...super.dependencies,
|
|
];
|
|
}
|
|
|
|
/// Build a profile iOS application bundle.
|
|
class ProfileIosApplicationBundle extends IosAssetBundle {
|
|
const ProfileIosApplicationBundle();
|
|
|
|
@override
|
|
String get name => 'profile_ios_bundle_flutter_assets';
|
|
|
|
@override
|
|
List<Target> get dependencies => const <Target>[
|
|
AotAssemblyProfile(),
|
|
];
|
|
}
|
|
|
|
/// Build a release iOS application bundle.
|
|
class ReleaseIosApplicationBundle extends IosAssetBundle {
|
|
const ReleaseIosApplicationBundle();
|
|
|
|
@override
|
|
String get name => 'release_ios_bundle_flutter_assets';
|
|
|
|
@override
|
|
List<Target> get dependencies => const <Target>[
|
|
AotAssemblyRelease(),
|
|
];
|
|
}
|
|
|
|
/// 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, { bool include32Bit = true }) async {
|
|
try {
|
|
outputFile.createSync(recursive: true);
|
|
} on Exception catch (e) {
|
|
throwToolExit('Failed to create App.framework stub at ${outputFile.path}: $e');
|
|
}
|
|
|
|
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>[
|
|
if (include32Bit)
|
|
...<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.
|
|
} on Exception catch (e) {
|
|
throwToolExit('Failed to create App.framework stub at ${outputFile.path}: $e');
|
|
}
|
|
}
|
|
}
|