mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Enable bitcode compilation for AOT (#36471)
This commit is contained in:
parent
e6128a0c14
commit
c953cd19d2
@ -114,6 +114,7 @@ BuildApp() {
|
||||
flutter_engine_flag="--local-engine-src-path=${FLUTTER_ENGINE}"
|
||||
fi
|
||||
|
||||
local bitcode_flag=""
|
||||
if [[ -n "$LOCAL_ENGINE" ]]; then
|
||||
if [[ $(echo "$LOCAL_ENGINE" | tr "[:upper:]" "[:lower:]") != *"$build_mode"* ]]; then
|
||||
EchoError "========================================================================"
|
||||
@ -130,6 +131,9 @@ BuildApp() {
|
||||
local_engine_flag="--local-engine=${LOCAL_ENGINE}"
|
||||
flutter_framework="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.framework"
|
||||
flutter_podspec="${FLUTTER_ENGINE}/out/${LOCAL_ENGINE}/Flutter.podspec"
|
||||
if [[ $ENABLE_BITCODE == "YES" ]]; then
|
||||
bitcode_flag="--bitcode"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [[ -e "${project_path}/.ios" ]]; then
|
||||
@ -174,6 +178,7 @@ BuildApp() {
|
||||
EchoError "========================================================================"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
|
||||
${verbose_flag} \
|
||||
build aot \
|
||||
@ -183,7 +188,8 @@ BuildApp() {
|
||||
--${build_mode} \
|
||||
--ios-arch="${archs}" \
|
||||
${flutter_engine_flag} \
|
||||
${local_engine_flag}
|
||||
${local_engine_flag} \
|
||||
${bitcode_flag}
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
EchoError "Failed to build ${project_path}."
|
||||
|
||||
@ -94,7 +94,13 @@ class AOTSnapshotter {
|
||||
@required String outputPath,
|
||||
IOSArch iosArch,
|
||||
List<String> extraGenSnapshotOptions = const <String>[],
|
||||
@required bool bitcode,
|
||||
}) async {
|
||||
if (bitcode && platform != TargetPlatform.ios) {
|
||||
printError('Bitcode is only supported for iOS.');
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (!_isValidAotPlatform(platform, buildMode)) {
|
||||
printError('${getNameForTargetPlatform(platform)} does not support AOT compilation.');
|
||||
return 1;
|
||||
@ -172,6 +178,21 @@ class AOTSnapshotter {
|
||||
return genSnapshotExitCode;
|
||||
}
|
||||
|
||||
// TODO(dnfield): This should be removed when https://github.com/dart-lang/sdk/issues/37560
|
||||
// is resolved.
|
||||
// The DWARF section confuses Xcode tooling, so this strips it. Ideally,
|
||||
// gen_snapshot would provide an argument to do this automatically.
|
||||
if (platform == TargetPlatform.ios && bitcode) {
|
||||
final IOSink sink = fs.file('$assembly.bitcode').openWrite();
|
||||
for (String line in await fs.file(assembly).readAsLines()) {
|
||||
if (line.startsWith('.section __DWARF')) {
|
||||
break;
|
||||
}
|
||||
sink.writeln(line);
|
||||
}
|
||||
await sink.close();
|
||||
}
|
||||
|
||||
// Write path to gen_snapshot, since snapshots have to be re-generated when we roll
|
||||
// the Dart SDK.
|
||||
final String genSnapshotPath = GenSnapshot.getSnapshotterPath(snapshotType);
|
||||
@ -180,7 +201,12 @@ class AOTSnapshotter {
|
||||
// On iOS, we use Xcode to compile the snapshot into a dynamic library that the
|
||||
// end-developer can link into their app.
|
||||
if (platform == TargetPlatform.ios) {
|
||||
final RunResult result = await _buildIosFramework(iosArch: iosArch, assemblyPath: assembly, outputPath: outputDir.path);
|
||||
final RunResult result = await _buildIosFramework(
|
||||
iosArch: iosArch,
|
||||
assemblyPath: bitcode ? '$assembly.bitcode' : assembly,
|
||||
outputPath: outputDir.path,
|
||||
bitcode: bitcode,
|
||||
);
|
||||
if (result.exitCode != 0)
|
||||
return result.exitCode;
|
||||
}
|
||||
@ -193,13 +219,21 @@ class AOTSnapshotter {
|
||||
@required IOSArch iosArch,
|
||||
@required String assemblyPath,
|
||||
@required String outputPath,
|
||||
@required bool bitcode,
|
||||
}) async {
|
||||
final String targetArch = iosArch == IOSArch.armv7 ? 'armv7' : 'arm64';
|
||||
printStatus('Building App.framework for $targetArch...');
|
||||
final List<String> commonBuildOptions = <String>['-arch', targetArch, '-miphoneos-version-min=8.0'];
|
||||
|
||||
final String assemblyO = fs.path.join(outputPath, 'snapshot_assembly.o');
|
||||
final RunResult compileResult = await xcode.cc(<String>[...commonBuildOptions, '-c', assemblyPath, '-o', assemblyO]);
|
||||
final RunResult compileResult = await xcode.cc(<String>[
|
||||
...commonBuildOptions,
|
||||
'-c',
|
||||
assemblyPath,
|
||||
'-o',
|
||||
assemblyO,
|
||||
if (bitcode) '-fembed-bitcode',
|
||||
]);
|
||||
if (compileResult.exitCode != 0) {
|
||||
printError('Failed to compile AOT snapshot. Compiler terminated with exit code ${compileResult.exitCode}');
|
||||
return compileResult;
|
||||
@ -214,14 +248,23 @@ class AOTSnapshotter {
|
||||
'-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
|
||||
'-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
|
||||
'-install_name', '@rpath/App.framework/App',
|
||||
if (bitcode) '-fembed-bitcode',
|
||||
'-o', appLib,
|
||||
assemblyO,
|
||||
];
|
||||
final RunResult linkResult = await xcode.clang(linkArgs);
|
||||
if (linkResult.exitCode != 0) {
|
||||
printError('Failed to link AOT snapshot. Linker terminated with exit code ${compileResult.exitCode}');
|
||||
return linkResult;
|
||||
}
|
||||
return linkResult;
|
||||
final RunResult dsymResult = await xcode.dsymutil(<String>[
|
||||
appLib,
|
||||
'-o', fs.path.join(outputPath, 'App.framework.dSYM'),
|
||||
]);
|
||||
if (dsymResult.exitCode != 0) {
|
||||
printError('Failed to extract dSYM out of dynamic lib');
|
||||
}
|
||||
return dsymResult;
|
||||
}
|
||||
|
||||
/// Compiles a Dart file to kernel.
|
||||
|
||||
@ -119,8 +119,6 @@ const List<String> _kBuildModes = <String>[
|
||||
'debug',
|
||||
'profile',
|
||||
'release',
|
||||
'dynamic-profile',
|
||||
'dynamic-release',
|
||||
];
|
||||
|
||||
/// Return the name for the build mode, or "any" if null.
|
||||
|
||||
@ -25,6 +25,9 @@ const String kTargetPlatform = 'TargetPlatform';
|
||||
/// The define to control what target file is used.
|
||||
const String kTargetFile = 'TargetFile';
|
||||
|
||||
/// The define to control whether the AOT snapshot is built with bitcode.
|
||||
const String kBitcodeFlag = 'EnableBitcode';
|
||||
|
||||
/// The define to control what iOS architectures are built for.
|
||||
///
|
||||
/// This is expected to be a comma-separated list of architectures. If not
|
||||
@ -74,6 +77,7 @@ Future<void> compileAotElf(Map<String, ChangeType> updates, Environment environm
|
||||
if (environment.defines[kTargetPlatform] == null) {
|
||||
throw MissingDefineException(kTargetPlatform, 'aot_elf');
|
||||
}
|
||||
|
||||
final BuildMode buildMode = getBuildModeForName(environment.defines[kBuildMode]);
|
||||
final TargetPlatform targetPlatform = getTargetPlatformForName(environment.defines[kTargetPlatform]);
|
||||
final int snapshotExitCode = await snapshotter.build(
|
||||
@ -82,6 +86,7 @@ Future<void> compileAotElf(Map<String, ChangeType> updates, Environment environm
|
||||
mainPath: environment.buildDir.childFile('main.app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
|
||||
@ -126,6 +131,7 @@ Future<void> compileAotAssembly(Map<String, ChangeType> updates, Environment env
|
||||
if (targetPlatform != TargetPlatform.ios) {
|
||||
throw Exception('aot_assembly is only supported for iOS applications');
|
||||
}
|
||||
final bool bitcode = environment.defines[kBitcodeFlag] == 'true';
|
||||
|
||||
// If we're building for a single architecture (common), then skip the lipo.
|
||||
if (iosArchs.length == 1) {
|
||||
@ -136,6 +142,7 @@ Future<void> compileAotAssembly(Map<String, ChangeType> updates, Environment env
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: outputPath,
|
||||
iosArch: iosArchs.single,
|
||||
bitcode: bitcode,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
throw Exception('AOT snapshotter exited with code $snapshotExitCode');
|
||||
@ -152,6 +159,7 @@ Future<void> compileAotAssembly(Map<String, ChangeType> updates, Environment env
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: fs.path.join(outputPath, getNameForIOSArch(iosArch)),
|
||||
iosArch: iosArch,
|
||||
bitcode: bitcode,
|
||||
));
|
||||
}
|
||||
final List<int> results = await Future.wait(pending);
|
||||
|
||||
@ -4,14 +4,18 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import '../artifacts.dart';
|
||||
import '../base/build.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/process.dart';
|
||||
import '../build_info.dart';
|
||||
import '../dart/package_map.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/ios_workflow.dart';
|
||||
import '../macos/xcode.dart';
|
||||
import '../resident_runner.dart';
|
||||
import '../runner/flutter_command.dart';
|
||||
import 'build.dart';
|
||||
@ -52,6 +56,11 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
|
||||
..addMultiOption(FlutterOptions.kExtraGenSnapshotOptions,
|
||||
splitCommas: true,
|
||||
hide: true,
|
||||
)
|
||||
..addFlag('bitcode',
|
||||
defaultsTo: false,
|
||||
help: 'Build the AOT bundle with bitcode. Requires a compatible bitcode engine.',
|
||||
hide: true,
|
||||
);
|
||||
}
|
||||
|
||||
@ -68,8 +77,16 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
|
||||
if (platform == null)
|
||||
throwToolExit('Unknown platform: $targetPlatform');
|
||||
|
||||
final bool bitcode = argResults['bitcode'];
|
||||
final BuildMode buildMode = getBuildMode();
|
||||
|
||||
if (bitcode) {
|
||||
if (platform != TargetPlatform.ios) {
|
||||
throwToolExit('Bitcode is only supported on iOS (TargetPlatform is $targetPlatform).');
|
||||
}
|
||||
await validateBitcode();
|
||||
}
|
||||
|
||||
Status status;
|
||||
if (!argResults['quiet']) {
|
||||
final String typeName = artifacts.getEngineType(platform, buildMode);
|
||||
@ -118,6 +135,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
|
||||
packagesPath: PackageMap.globalPackagesPath,
|
||||
outputPath: outputPath,
|
||||
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
|
||||
bitcode: bitcode,
|
||||
).then<int>((int buildExitCode) {
|
||||
return buildExitCode;
|
||||
});
|
||||
@ -131,8 +149,15 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
|
||||
'lipo',
|
||||
...dylibs,
|
||||
'-create',
|
||||
'-output',
|
||||
fs.path.join(outputPath, 'App.framework', 'App'),
|
||||
'-output', fs.path.join(outputPath, 'App.framework', 'App'),
|
||||
]);
|
||||
final Iterable<String> dSYMs = iosBuilds.values.map<String>((String outputDir) => fs.path.join(outputDir, 'App.framework.dSYM'));
|
||||
fs.directory(fs.path.join(outputPath, 'App.framework.dSYM', 'Contents', 'Resources', 'DWARF'))..createSync(recursive: true);
|
||||
await runCheckedAsync(<String>[
|
||||
'lipo',
|
||||
'-create',
|
||||
'-output', fs.path.join(outputPath, 'App.framework.dSYM', 'Contents', 'Resources', 'DWARF', 'App'),
|
||||
...dSYMs.map((String path) => fs.path.join(path, 'Contents', 'Resources', 'DWARF', 'App'))
|
||||
]);
|
||||
} else {
|
||||
status?.cancel();
|
||||
@ -150,6 +175,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
|
||||
packagesPath: PackageMap.globalPackagesPath,
|
||||
outputPath: outputPath,
|
||||
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
|
||||
bitcode: false,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
status?.cancel();
|
||||
@ -176,3 +202,38 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> validateBitcode() async {
|
||||
final Artifacts artifacts = Artifacts.instance;
|
||||
if (artifacts is! LocalEngineArtifacts) {
|
||||
throwToolExit('Bitcode is only supported with a local engine built with --bitcode.');
|
||||
}
|
||||
final String flutterFrameworkPath = artifacts.getArtifactPath(Artifact.flutterFramework);
|
||||
if (!fs.isDirectorySync(flutterFrameworkPath)) {
|
||||
throwToolExit('Flutter.framework not found at $flutterFrameworkPath');
|
||||
}
|
||||
final Xcode xcode = context.get<Xcode>();
|
||||
|
||||
// Check for bitcode in Flutter binary.
|
||||
final RunResult otoolResult = await xcode.otool(<String>[
|
||||
'-l', fs.path.join(flutterFrameworkPath, 'Flutter'),
|
||||
]);
|
||||
if (!otoolResult.stdout.contains('__LLVM')) {
|
||||
throwToolExit('The Flutter.framework at $flutterFrameworkPath does not contain bitcode.');
|
||||
}
|
||||
final RunResult clangResult = await xcode.clang(<String>['--version']);
|
||||
final String clangVersion = clangResult.stdout.split('\n').first;
|
||||
final String engineClangVersion = iosWorkflow.getPlistValueFromFile(
|
||||
fs.path.join(flutterFrameworkPath, 'Info.plist'),
|
||||
'ClangVersion',
|
||||
);
|
||||
if (clangVersion != engineClangVersion) {
|
||||
printStatus(
|
||||
'The Flutter.framework at $flutterFrameworkPath was built '
|
||||
'with "${engineClangVersion ?? 'unknown'}", but the current version '
|
||||
'of clang is "$clangVersion". This may result in failures when '
|
||||
'archiving your application in Xcode.',
|
||||
emphasis: true,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,6 +469,18 @@ String readGeneratedXcconfig(String appPath) {
|
||||
}
|
||||
|
||||
Future<void> diagnoseXcodeBuildFailure(XcodeBuildResult result) async {
|
||||
if (result.xcodeBuildExecution != null &&
|
||||
result.xcodeBuildExecution.buildForPhysicalDevice &&
|
||||
result.stdout?.toUpperCase()?.contains('BITCODE') == true) {
|
||||
flutterUsage.sendEvent(
|
||||
'Xcode',
|
||||
'bitcode-failure',
|
||||
parameters: <String, String>{
|
||||
'build-commands': result.xcodeBuildExecution.buildCommands.toString(),
|
||||
'build-settings': result.xcodeBuildExecution.buildSettings.toString(),
|
||||
});
|
||||
}
|
||||
|
||||
if (result.xcodeBuildExecution != null &&
|
||||
result.xcodeBuildExecution.buildForPhysicalDevice &&
|
||||
result.stdout?.contains('BCEROR') == true &&
|
||||
|
||||
@ -97,6 +97,18 @@ class Xcode {
|
||||
return runCheckedAsync(<String>['xcrun', 'clang', ...args]);
|
||||
}
|
||||
|
||||
Future<RunResult> dsymutil(List<String> args) {
|
||||
return runCheckedAsync(<String>['xcrun', 'dsymutil', ...args]);
|
||||
}
|
||||
|
||||
Future<RunResult> strip(List<String> args) {
|
||||
return runCheckedAsync(<String>['xcrun', 'strip', ...args]);
|
||||
}
|
||||
|
||||
Future<RunResult> otool(List<String> args) {
|
||||
return runCheckedAsync(<String>['xcrun', 'otool', ...args]);
|
||||
}
|
||||
|
||||
String getSimulatorPath() {
|
||||
if (xcodeSelectPath == null)
|
||||
return null;
|
||||
|
||||
@ -116,6 +116,13 @@ void main() {
|
||||
when(mockArtifacts.getArtifactPath(Artifact.snapshotDart,
|
||||
platform: anyNamed('platform'), mode: mode)).thenReturn(kSnapshotDart);
|
||||
}
|
||||
|
||||
when(mockXcode.dsymutil(any)).thenAnswer((_) => Future<RunResult>.value(
|
||||
RunResult(
|
||||
ProcessResult(1, 0, '', ''),
|
||||
<String>['command name', 'arguments...']),
|
||||
),
|
||||
);
|
||||
});
|
||||
|
||||
final Map<Type, Generator> contextOverrides = <Type, Generator>{
|
||||
@ -135,6 +142,7 @@ void main() {
|
||||
mainPath: 'main.dill',
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
), isNot(equals(0)));
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
@ -146,6 +154,7 @@ void main() {
|
||||
mainPath: 'main.dill',
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
), isNot(0));
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
@ -157,17 +166,19 @@ void main() {
|
||||
mainPath: 'main.dill',
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
), isNot(0));
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
testUsingContext('builds iOS armv7 profile AOT snapshot', () async {
|
||||
testUsingContext('iOS debug AOT with bitcode uses right flags', () async {
|
||||
fs.file('main.dill').writeAsStringSync('binary magic');
|
||||
|
||||
final String outputPath = fs.path.join('build', 'foo');
|
||||
fs.directory(outputPath).createSync(recursive: true);
|
||||
|
||||
final String assembly = fs.path.join(outputPath, 'snapshot_assembly.S');
|
||||
genSnapshot.outputs = <String, String>{
|
||||
fs.path.join(outputPath, 'snapshot_assembly.S'): '',
|
||||
assembly: 'blah blah\n.section __DWARF\nblah blah\n',
|
||||
};
|
||||
|
||||
final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
|
||||
@ -181,6 +192,7 @@ void main() {
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
iosArch: IOSArch.armv7,
|
||||
bitcode: true,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -190,11 +202,70 @@ void main() {
|
||||
expect(genSnapshot.additionalArgs, <String>[
|
||||
'--deterministic',
|
||||
'--snapshot_kind=app-aot-assembly',
|
||||
'--assembly=${fs.path.join(outputPath, 'snapshot_assembly.S')}',
|
||||
'--assembly=$assembly',
|
||||
'--no-sim-use-hardfp',
|
||||
'--no-use-integer-division',
|
||||
'main.dill',
|
||||
]);
|
||||
|
||||
verify(xcode.cc(argThat(contains('-fembed-bitcode')))).called(1);
|
||||
verify(xcode.clang(argThat(contains('-fembed-bitcode')))).called(1);
|
||||
verify(xcode.dsymutil(any)).called(1);
|
||||
|
||||
final File assemblyFile = fs.file(assembly);
|
||||
final File assemblyBitcodeFile = fs.file('$assembly.bitcode');
|
||||
expect(assemblyFile.existsSync(), true);
|
||||
expect(assemblyBitcodeFile.existsSync(), true);
|
||||
expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
|
||||
expect(assemblyBitcodeFile.readAsStringSync().contains('.section __DWARF'), false);
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
testUsingContext('builds iOS armv7 profile AOT snapshot', () async {
|
||||
fs.file('main.dill').writeAsStringSync('binary magic');
|
||||
|
||||
final String outputPath = fs.path.join('build', 'foo');
|
||||
fs.directory(outputPath).createSync(recursive: true);
|
||||
|
||||
final String assembly = fs.path.join(outputPath, 'snapshot_assembly.S');
|
||||
genSnapshot.outputs = <String, String>{
|
||||
assembly: 'blah blah\n.section __DWARF\nblah blah\n',
|
||||
};
|
||||
|
||||
final RunResult successResult = RunResult(ProcessResult(1, 0, '', ''), <String>['command name', 'arguments...']);
|
||||
when(xcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
|
||||
when(xcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(successResult));
|
||||
|
||||
final int genSnapshotExitCode = await snapshotter.build(
|
||||
platform: TargetPlatform.ios,
|
||||
buildMode: BuildMode.profile,
|
||||
mainPath: 'main.dill',
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
iosArch: IOSArch.armv7,
|
||||
bitcode: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
expect(genSnapshot.callCount, 1);
|
||||
expect(genSnapshot.snapshotType.platform, TargetPlatform.ios);
|
||||
expect(genSnapshot.snapshotType.mode, BuildMode.profile);
|
||||
expect(genSnapshot.additionalArgs, <String>[
|
||||
'--deterministic',
|
||||
'--snapshot_kind=app-aot-assembly',
|
||||
'--assembly=$assembly',
|
||||
'--no-sim-use-hardfp',
|
||||
'--no-use-integer-division',
|
||||
'main.dill',
|
||||
]);
|
||||
verifyNever(xcode.cc(argThat(contains('-fembed-bitcode'))));
|
||||
verifyNever(xcode.clang(argThat(contains('-fembed-bitcode'))));
|
||||
verify(xcode.dsymutil(any)).called(1);
|
||||
|
||||
final File assemblyFile = fs.file(assembly);
|
||||
final File assemblyBitcodeFile = fs.file('$assembly.bitcode');
|
||||
expect(assemblyFile.existsSync(), true);
|
||||
expect(assemblyBitcodeFile.existsSync(), false);
|
||||
expect(assemblyFile.readAsStringSync().contains('.section __DWARF'), true);
|
||||
}, overrides: contextOverrides);
|
||||
|
||||
testUsingContext('builds iOS arm64 profile AOT snapshot', () async {
|
||||
@ -218,6 +289,7 @@ void main() {
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
iosArch: IOSArch.arm64,
|
||||
bitcode: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -253,6 +325,7 @@ void main() {
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
iosArch: IOSArch.armv7,
|
||||
bitcode: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -290,6 +363,7 @@ void main() {
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
iosArch: IOSArch.arm64,
|
||||
bitcode: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -316,6 +390,7 @@ void main() {
|
||||
mainPath: 'main.dill',
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -345,6 +420,7 @@ void main() {
|
||||
mainPath: 'main.dill',
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
@ -380,6 +456,7 @@ void main() {
|
||||
mainPath: 'main.dill',
|
||||
packagesPath: '.packages',
|
||||
outputPath: outputPath,
|
||||
bitcode: false,
|
||||
);
|
||||
|
||||
expect(genSnapshotExitCode, 0);
|
||||
|
||||
@ -5,12 +5,14 @@
|
||||
import 'package:flutter_tools/src/base/build.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/base/platform.dart';
|
||||
import 'package:flutter_tools/src/base/process.dart';
|
||||
import 'package:flutter_tools/src/build_info.dart';
|
||||
import 'package:flutter_tools/src/build_system/build_system.dart';
|
||||
import 'package:flutter_tools/src/build_system/exceptions.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/dart.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/compile.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
@ -26,6 +28,7 @@ void main() {
|
||||
Environment androidEnvironment;
|
||||
Environment iosEnvironment;
|
||||
MockProcessManager mockProcessManager;
|
||||
MockXcode mockXcode;
|
||||
|
||||
setUpAll(() {
|
||||
Cache.disableLocking();
|
||||
@ -33,6 +36,7 @@ void main() {
|
||||
|
||||
setUp(() {
|
||||
mockProcessManager = MockProcessManager();
|
||||
mockXcode = MockXcode();
|
||||
testbed = Testbed(setup: () {
|
||||
androidEnvironment = Environment(
|
||||
projectDir: fs.currentDirectory,
|
||||
@ -153,8 +157,70 @@ flutter_tools:lib/''');
|
||||
expect(result.exceptions.values.single.exception, isInstanceOf<Exception>());
|
||||
}));
|
||||
|
||||
test('aot_assembly_profile with bitcode sends correct argument to snapshotter (one arch)', () => testbed.run(() async {
|
||||
iosEnvironment.defines[kIosArchs] = 'arm64';
|
||||
iosEnvironment.defines[kBitcodeFlag] = 'true';
|
||||
|
||||
final FakeProcessResult fakeProcessResult = FakeProcessResult(
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
);
|
||||
final RunResult fakeRunResult = RunResult(fakeProcessResult, const <String>['foo']);
|
||||
when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
|
||||
fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
|
||||
.createSync(recursive: true);
|
||||
return fakeProcessResult;
|
||||
});
|
||||
|
||||
when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
when(mockXcode.dsymutil(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
|
||||
final BuildResult result = await buildSystem.build('aot_assembly_profile',
|
||||
iosEnvironment, const BuildSystemConfig());
|
||||
|
||||
expect(result.success, true);
|
||||
verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(1);
|
||||
verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(1);
|
||||
verify(mockXcode.dsymutil(any)).called(1);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Xcode: () => mockXcode,
|
||||
}));
|
||||
|
||||
test('aot_assembly_profile with bitcode sends correct argument to snapshotter (mutli arch)', () => testbed.run(() async {
|
||||
iosEnvironment.defines[kIosArchs] = 'armv7,arm64';
|
||||
iosEnvironment.defines[kBitcodeFlag] = 'true';
|
||||
|
||||
final FakeProcessResult fakeProcessResult = FakeProcessResult(
|
||||
stdout: '',
|
||||
stderr: '',
|
||||
);
|
||||
final RunResult fakeRunResult = RunResult(fakeProcessResult, const <String>['foo']);
|
||||
when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
|
||||
fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
|
||||
.createSync(recursive: true);
|
||||
return fakeProcessResult;
|
||||
});
|
||||
|
||||
when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
when(mockXcode.dsymutil(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
|
||||
final BuildResult result = await buildSystem.build('aot_assembly_profile',
|
||||
iosEnvironment, const BuildSystemConfig());
|
||||
|
||||
expect(result.success, true);
|
||||
verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(2);
|
||||
verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(2);
|
||||
verify(mockXcode.dsymutil(any)).called(2);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Xcode: () => mockXcode,
|
||||
}));
|
||||
|
||||
test('aot_assembly_profile will lipo binaries together when multiple archs are requested', () => testbed.run(() async {
|
||||
iosEnvironment.defines[kIosArchs] ='armv7,arm64';
|
||||
iosEnvironment.defines[kIosArchs] = 'armv7,arm64';
|
||||
when(mockProcessManager.run(any)).thenAnswer((Invocation invocation) async {
|
||||
fs.file(fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
|
||||
.createSync(recursive: true);
|
||||
@ -175,18 +241,26 @@ flutter_tools:lib/''');
|
||||
|
||||
class MockProcessManager extends Mock implements ProcessManager {}
|
||||
|
||||
class MockXcode extends Mock implements Xcode {}
|
||||
|
||||
class FakeGenSnapshot implements GenSnapshot {
|
||||
List<String> lastCallAdditionalArgs;
|
||||
@override
|
||||
Future<int> run({SnapshotType snapshotType, IOSArch iosArch, Iterable<String> additionalArgs = const <String>[]}) async {
|
||||
final Directory out = fs.file(additionalArgs.last).parent;
|
||||
lastCallAdditionalArgs = additionalArgs.toList();
|
||||
final Directory out = fs.file(lastCallAdditionalArgs.last).parent;
|
||||
if (iosArch == null) {
|
||||
out.childFile('app.so').createSync();
|
||||
out.childFile('gen_snapshot.d').createSync();
|
||||
return 0;
|
||||
}
|
||||
out.childDirectory('App.framework').childFile('App').createSync(recursive: true);
|
||||
out.childFile('snapshot_assembly.S').createSync();
|
||||
out.childFile('snapshot_assembly.o').createSync();
|
||||
|
||||
final String assembly = lastCallAdditionalArgs
|
||||
.firstWhere((String arg) => arg.startsWith('--assembly'))
|
||||
.substring('--assembly='.length);
|
||||
fs.file(assembly).createSync();
|
||||
fs.file(assembly.replaceAll('.S', '.o')).createSync();
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,139 @@
|
||||
// Copyright 2019 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:file/memory.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/logger.dart';
|
||||
import 'package:flutter_tools/src/base/process.dart';
|
||||
import 'package:flutter_tools/src/commands/build_aot.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.dart';
|
||||
import 'package:flutter_tools/src/ios/ios_workflow.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:process/process.dart';
|
||||
|
||||
import '../../src/common.dart';
|
||||
import '../../src/context.dart';
|
||||
import '../../src/mocks.dart';
|
||||
|
||||
void main() {
|
||||
MockXcode mockXcode;
|
||||
MemoryFileSystem memoryFileSystem;
|
||||
MockProcessManager mockProcessManager;
|
||||
BufferLogger bufferLogger;
|
||||
MockIOSWorkflow mockIOSWorkflow;
|
||||
|
||||
setUp(() {
|
||||
mockXcode = MockXcode();
|
||||
memoryFileSystem = MemoryFileSystem(style: FileSystemStyle.posix);
|
||||
mockProcessManager = MockProcessManager();
|
||||
bufferLogger = BufferLogger();
|
||||
mockIOSWorkflow = MockIOSWorkflow();
|
||||
});
|
||||
|
||||
testUsingContext('build aot validates building with bitcode requires a local engine', () async {
|
||||
await expectToolExitLater(
|
||||
validateBitcode(),
|
||||
equals('Bitcode is only supported with a local engine built with --bitcode.'),
|
||||
);
|
||||
});
|
||||
|
||||
testUsingContext('build aot validates existence of Flutter.framework in engine', () async {
|
||||
await expectToolExitLater(
|
||||
validateBitcode(),
|
||||
equals('Flutter.framework not found at ios_profile/Flutter.framework'),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => LocalEngineArtifacts('/engine', 'ios_profile', 'host_profile'),
|
||||
FileSystem: () => memoryFileSystem,
|
||||
});
|
||||
|
||||
testUsingContext('build aot validates Flutter.framework/Flutter contains bitcode', () async {
|
||||
final Directory flutterFramework = memoryFileSystem.directory('ios_profile/Flutter.framework')
|
||||
..createSync(recursive: true);
|
||||
flutterFramework.childFile('Flutter').createSync();
|
||||
flutterFramework.childFile('Info.plist').createSync();
|
||||
|
||||
final RunResult otoolResult = RunResult(
|
||||
FakeProcessResult(stdout: '', stderr: ''),
|
||||
const <String>['foo'],
|
||||
);
|
||||
when(mockXcode.otool(any)).thenAnswer((_) => Future<RunResult>.value(otoolResult));
|
||||
await expectToolExitLater(
|
||||
validateBitcode(),
|
||||
equals('The Flutter.framework at ios_profile/Flutter.framework does not contain bitcode.'),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => LocalEngineArtifacts('/engine', 'ios_profile', 'host_profile'),
|
||||
FileSystem: () => memoryFileSystem,
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Xcode: () => mockXcode,
|
||||
});
|
||||
|
||||
testUsingContext('build aot validates Flutter.framework/Flutter was built with same toolchain', () async {
|
||||
final Directory flutterFramework = memoryFileSystem.directory('ios_profile/Flutter.framework')
|
||||
..createSync(recursive: true);
|
||||
flutterFramework.childFile('Flutter').createSync();
|
||||
final File infoPlist = flutterFramework.childFile('Info.plist')..createSync();
|
||||
|
||||
final RunResult otoolResult = RunResult(
|
||||
FakeProcessResult(stdout: '__LLVM', stderr: ''),
|
||||
const <String>['foo'],
|
||||
);
|
||||
final RunResult clangResult = RunResult(
|
||||
FakeProcessResult(stdout: 'BadVersion\nBlahBlah\n', stderr: ''),
|
||||
const <String>['foo'],
|
||||
);
|
||||
when(mockXcode.otool(any)).thenAnswer((_) => Future<RunResult>.value(otoolResult));
|
||||
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(clangResult));
|
||||
when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM Version 10.0.1');
|
||||
|
||||
await validateBitcode();
|
||||
|
||||
expect(
|
||||
bufferLogger.statusText,
|
||||
startsWith('The Flutter.framework at ${flutterFramework.path} was built with "Apple LLVM Version 10.0.1'),
|
||||
);
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => LocalEngineArtifacts('/engine', 'ios_profile', 'host_profile'),
|
||||
FileSystem: () => memoryFileSystem,
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Xcode: () => mockXcode,
|
||||
Logger: () => bufferLogger,
|
||||
IOSWorkflow: () => mockIOSWorkflow,
|
||||
});
|
||||
|
||||
testUsingContext('build aot validates and succeeds', () async {
|
||||
final Directory flutterFramework = memoryFileSystem.directory('ios_profile/Flutter.framework')
|
||||
..createSync(recursive: true);
|
||||
flutterFramework.childFile('Flutter').createSync();
|
||||
final File infoPlist = flutterFramework.childFile('Info.plist')..createSync();
|
||||
|
||||
final RunResult otoolResult = RunResult(
|
||||
FakeProcessResult(stdout: '__LLVM', stderr: ''),
|
||||
const <String>['foo'],
|
||||
);
|
||||
final RunResult clangResult = RunResult(
|
||||
FakeProcessResult(stdout: 'Apple LLVM Version 10.0.1\nBlahBlah\n', stderr: ''),
|
||||
const <String>['foo'],
|
||||
);
|
||||
when(mockXcode.otool(any)).thenAnswer((_) => Future<RunResult>.value(otoolResult));
|
||||
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(clangResult));
|
||||
when(mockIOSWorkflow.getPlistValueFromFile(infoPlist.path, 'ClangVersion')).thenReturn('Apple LLVM Version 10.0.1');
|
||||
|
||||
await validateBitcode();
|
||||
|
||||
expect(bufferLogger.statusText, '');
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => LocalEngineArtifacts('/engine', 'ios_profile', 'host_profile'),
|
||||
FileSystem: () => memoryFileSystem,
|
||||
ProcessManager: () => mockProcessManager,
|
||||
Xcode: () => mockXcode,
|
||||
Logger: () => bufferLogger,
|
||||
IOSWorkflow: () => mockIOSWorkflow,
|
||||
});
|
||||
}
|
||||
|
||||
class MockXcode extends Mock implements Xcode {}
|
||||
class MockIOSWorkflow extends Mock implements IOSWorkflow {}
|
||||
@ -12,6 +12,7 @@ import 'package:flutter_tools/src/ios/xcodeproj.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/cache.dart';
|
||||
import 'package:flutter_tools/src/project.dart';
|
||||
import 'package:flutter_tools/src/reporting/usage.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
import 'package:platform/platform.dart';
|
||||
import 'package:process/process.dart';
|
||||
@ -159,11 +160,35 @@ void main() {
|
||||
|
||||
group('Diagnose Xcode build failure', () {
|
||||
Map<String, String> buildSettings;
|
||||
MockUsage mockUsage;
|
||||
|
||||
setUp(() {
|
||||
buildSettings = <String, String>{
|
||||
'PRODUCT_BUNDLE_IDENTIFIER': 'test.app',
|
||||
};
|
||||
mockUsage = MockUsage();
|
||||
});
|
||||
|
||||
testUsingContext('Sends analytics when bitcode fails', () async {
|
||||
const List<String> buildCommands = <String>['xcrun', 'cc', 'blah'];
|
||||
final XcodeBuildResult buildResult = XcodeBuildResult(
|
||||
success: false,
|
||||
stdout: 'BITCODE_ENABLED = YES',
|
||||
xcodeBuildExecution: XcodeBuildExecution(
|
||||
buildCommands: buildCommands,
|
||||
appDirectory: '/blah/blah',
|
||||
buildForPhysicalDevice: true,
|
||||
buildSettings: buildSettings,
|
||||
),
|
||||
);
|
||||
|
||||
await diagnoseXcodeBuildFailure(buildResult);
|
||||
verify(mockUsage.sendEvent('Xcode', 'bitcode-failure', parameters: <String, String>{
|
||||
'build-commands': buildCommands.toString(),
|
||||
'build-settings': buildSettings.toString(),
|
||||
})).called(1);
|
||||
}, overrides: <Type, Generator>{
|
||||
Usage: () => mockUsage,
|
||||
});
|
||||
|
||||
testUsingContext('No provisioning profile shows message', () async {
|
||||
@ -379,3 +404,5 @@ Could not build the precompiled application for the device.''',
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
class MockUsage extends Mock implements Usage {}
|
||||
|
||||
@ -533,17 +533,6 @@ void transfer(FileSystemEntity entity, FileSystem target) {
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> expectToolExitLater(Future<dynamic> future, Matcher messageMatcher) async {
|
||||
try {
|
||||
await future;
|
||||
fail('ToolExit expected, but nothing thrown');
|
||||
} on ToolExit catch(e) {
|
||||
expect(e.message, messageMatcher);
|
||||
} catch(e, trace) {
|
||||
fail('ToolExit expected, got $e\n$trace');
|
||||
}
|
||||
}
|
||||
|
||||
void expectExists(FileSystemEntity entity) {
|
||||
expect(entity.existsSync(), isTrue);
|
||||
}
|
||||
|
||||
@ -141,3 +141,14 @@ const Timeout allowForRemotePubInvocation = Timeout.factor(10.0);
|
||||
/// Test case timeout for tests involving creating a Flutter project with
|
||||
/// `--no-pub`. Use [allowForRemotePubInvocation] when creation involves `pub`.
|
||||
const Timeout allowForCreateFlutterProject = Timeout.factor(3.0);
|
||||
|
||||
Future<void> expectToolExitLater(Future<dynamic> future, Matcher messageMatcher) async {
|
||||
try {
|
||||
await future;
|
||||
fail('ToolExit expected, but nothing thrown');
|
||||
} on ToolExit catch(e) {
|
||||
expect(e.message, messageMatcher);
|
||||
} catch(e, trace) {
|
||||
fail('ToolExit expected, got $e\n$trace');
|
||||
}
|
||||
}
|
||||
|
||||
@ -606,4 +606,7 @@ class FakeProcessResult implements ProcessResult {
|
||||
|
||||
@override
|
||||
final dynamic stdout;
|
||||
|
||||
@override
|
||||
String toString() => stdout?.toString() ?? stderr?.toString() ?? runtimeType.toString();
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user