[flutter_tools] Reland flutter assemble for iOS (#50229)
This commit is contained in:
parent
91c342e213
commit
de7908f9e9
@ -270,10 +270,10 @@ Future<void> main() async {
|
||||
final String objectiveCAnalyticsOutput = objectiveCAnalyticsOutputFile.readAsStringSync();
|
||||
if (!objectiveCAnalyticsOutput.contains('cd24: ios')
|
||||
|| !objectiveCAnalyticsOutput.contains('cd25: true')
|
||||
|| !objectiveCAnalyticsOutput.contains('viewName: build/bundle')) {
|
||||
|| !objectiveCAnalyticsOutput.contains('viewName: assemble')) {
|
||||
return TaskResult.failure(
|
||||
'Building outer Objective-C app produced the following analytics: "$objectiveCAnalyticsOutput" '
|
||||
'but not the expected strings: "cd24: ios", "cd25: true", "viewName: build/bundle"'
|
||||
'but not the expected strings: "cd24: ios", "cd25: true", "viewName: assemble"'
|
||||
);
|
||||
}
|
||||
|
||||
@ -359,10 +359,10 @@ Future<void> main() async {
|
||||
final String swiftAnalyticsOutput = swiftAnalyticsOutputFile.readAsStringSync();
|
||||
if (!swiftAnalyticsOutput.contains('cd24: ios')
|
||||
|| !swiftAnalyticsOutput.contains('cd25: true')
|
||||
|| !swiftAnalyticsOutput.contains('viewName: build/bundle')) {
|
||||
|| !swiftAnalyticsOutput.contains('viewName: assemble')) {
|
||||
return TaskResult.failure(
|
||||
'Building outer Swift app produced the following analytics: "$swiftAnalyticsOutput" '
|
||||
'but not the expected strings: "cd24: ios", "cd25: true", "viewName: build/bundle"'
|
||||
'but not the expected strings: "cd24: ios", "cd25: true", "viewName: assemble"'
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -51,6 +51,10 @@ BuildApp() {
|
||||
derived_dir="${project_path}/.ios/Flutter"
|
||||
fi
|
||||
|
||||
RunCommand mkdir -p -- "$derived_dir"
|
||||
AssertExists "$derived_dir"
|
||||
RunCommand rm -rf -- "${derived_dir}/App.framework"
|
||||
|
||||
# Default value of assets_path is flutter_assets
|
||||
local assets_path="flutter_assets"
|
||||
# The value of assets_path can set by add FLTAssetsPath to AppFrameworkInfo.plist
|
||||
@ -96,15 +100,6 @@ BuildApp() {
|
||||
fi
|
||||
|
||||
local framework_path="${FLUTTER_ROOT}/bin/cache/artifacts/engine/${artifact_variant}"
|
||||
|
||||
AssertExists "${framework_path}"
|
||||
AssertExists "${project_path}"
|
||||
|
||||
RunCommand mkdir -p -- "$derived_dir"
|
||||
AssertExists "$derived_dir"
|
||||
|
||||
RunCommand rm -rf -- "${derived_dir}/App.framework"
|
||||
|
||||
local flutter_engine_flag=""
|
||||
local local_engine_flag=""
|
||||
local flutter_framework="${framework_path}/Flutter.framework"
|
||||
@ -134,9 +129,10 @@ BuildApp() {
|
||||
|
||||
local bitcode_flag=""
|
||||
if [[ $ENABLE_BITCODE == "YES" ]]; then
|
||||
bitcode_flag="--bitcode"
|
||||
bitcode_flag="true"
|
||||
fi
|
||||
|
||||
# TODO(jonahwilliams): move engine copying to build system.
|
||||
if [[ -e "${project_path}/.ios" ]]; then
|
||||
RunCommand rm -rf -- "${derived_dir}/engine"
|
||||
mkdir "${derived_dir}/engine"
|
||||
@ -150,102 +146,29 @@ BuildApp() {
|
||||
|
||||
RunCommand pushd "${project_path}" > /dev/null
|
||||
|
||||
AssertExists "${target_path}"
|
||||
|
||||
local verbose_flag=""
|
||||
if [[ -n "$VERBOSE_SCRIPT_LOGGING" ]]; then
|
||||
verbose_flag="--verbose"
|
||||
fi
|
||||
|
||||
local build_dir="${FLUTTER_BUILD_DIR:-build}"
|
||||
|
||||
local track_widget_creation_flag=""
|
||||
if [[ -n "$TRACK_WIDGET_CREATION" ]]; then
|
||||
track_widget_creation_flag="--track-widget-creation"
|
||||
track_widget_creation_flag="true"
|
||||
fi
|
||||
|
||||
if [[ "${build_mode}" != "debug" ]]; then
|
||||
StreamOutput " ├─Building Dart code..."
|
||||
# Transform ARCHS to comma-separated list of target architectures.
|
||||
local archs="${ARCHS// /,}"
|
||||
if [[ $archs =~ .*i386.* || $archs =~ .*x86_64.* ]]; then
|
||||
EchoError "========================================================================"
|
||||
EchoError "ERROR: Flutter does not support running in profile or release mode on"
|
||||
EchoError "the Simulator (this build was: '$build_mode')."
|
||||
EchoError "You can ensure Flutter runs in Debug mode with your host app in release"
|
||||
EchoError "mode by setting FLUTTER_BUILD_MODE=debug in the .xcconfig associated"
|
||||
EchoError "with the ${CONFIGURATION} build configuration."
|
||||
EchoError "========================================================================"
|
||||
exit -1
|
||||
fi
|
||||
|
||||
RunCommand "${FLUTTER_ROOT}/bin/flutter" --suppress-analytics \
|
||||
${verbose_flag} \
|
||||
build aot \
|
||||
--output-dir="${build_dir}/aot" \
|
||||
--target-platform=ios \
|
||||
--target="${target_path}" \
|
||||
--${build_mode} \
|
||||
--ios-arch="${archs}" \
|
||||
${flutter_engine_flag} \
|
||||
${local_engine_flag} \
|
||||
${bitcode_flag}
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
EchoError "Failed to build ${project_path}."
|
||||
exit -1
|
||||
fi
|
||||
StreamOutput "done"
|
||||
|
||||
local app_framework="${build_dir}/aot/App.framework"
|
||||
|
||||
RunCommand cp -r -- "${app_framework}" "${derived_dir}"
|
||||
|
||||
else
|
||||
RunCommand mkdir -p -- "${derived_dir}/App.framework"
|
||||
|
||||
# Build stub for all requested architectures.
|
||||
local arch_flags=""
|
||||
read -r -a archs <<< "$ARCHS"
|
||||
for arch in "${archs[@]}"; do
|
||||
arch_flags="${arch_flags}-arch $arch "
|
||||
done
|
||||
|
||||
RunCommand eval "$(echo "static const int Moo = 88;" | xcrun clang -x c \
|
||||
${arch_flags} \
|
||||
-fembed-bitcode-marker \
|
||||
-dynamiclib \
|
||||
-Xlinker -rpath -Xlinker '@executable_path/Frameworks' \
|
||||
-Xlinker -rpath -Xlinker '@loader_path/Frameworks' \
|
||||
-install_name '@rpath/App.framework/App' \
|
||||
-o "${derived_dir}/App.framework/App" -)"
|
||||
fi
|
||||
|
||||
local plistPath="${project_path}/ios/Flutter/AppFrameworkInfo.plist"
|
||||
if [[ -e "${project_path}/.ios" ]]; then
|
||||
plistPath="${project_path}/.ios/Flutter/AppFrameworkInfo.plist"
|
||||
fi
|
||||
|
||||
RunCommand cp -- "$plistPath" "${derived_dir}/App.framework/Info.plist"
|
||||
|
||||
local precompilation_flag=""
|
||||
if [[ "$CURRENT_ARCH" != "x86_64" ]] && [[ "$build_mode" != "debug" ]]; then
|
||||
precompilation_flag="--precompiled"
|
||||
fi
|
||||
|
||||
StreamOutput " ├─Assembling Flutter resources..."
|
||||
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
|
||||
${verbose_flag} \
|
||||
build bundle \
|
||||
--target-platform=ios \
|
||||
--target="${target_path}" \
|
||||
--${build_mode} \
|
||||
--depfile="${build_dir}/snapshot_blob.bin.d" \
|
||||
--asset-dir="${derived_dir}/App.framework/${assets_path}" \
|
||||
${precompilation_flag} \
|
||||
${flutter_engine_flag} \
|
||||
${local_engine_flag} \
|
||||
${track_widget_creation_flag}
|
||||
RunCommand "${FLUTTER_ROOT}/bin/flutter" \
|
||||
${verbose_flag} \
|
||||
${flutter_engine_flag} \
|
||||
${local_engine_flag} \
|
||||
assemble \
|
||||
--output="${derived_dir}/" \
|
||||
-dTargetPlatform=ios \
|
||||
-dTargetFile="${target_path}" \
|
||||
-dBuildMode=${build_mode} \
|
||||
-dIosArchs="${ARCHS}" \
|
||||
-dTrackWidgetCreation="${track_widget_creation_flag}" \
|
||||
-dEnableBitcode="${bitcode_flag}" \
|
||||
"${build_mode}_ios_bundle_flutter_assets"
|
||||
|
||||
if [[ $? -ne 0 ]]; then
|
||||
EchoError "Failed to package ${project_path}."
|
||||
|
@ -12,14 +12,9 @@ import 'base/io.dart';
|
||||
import 'base/logger.dart';
|
||||
import 'base/process.dart';
|
||||
import 'build_info.dart';
|
||||
import 'build_system/build_system.dart';
|
||||
import 'build_system/targets/dart.dart';
|
||||
import 'build_system/targets/ios.dart';
|
||||
import 'cache.dart';
|
||||
import 'dart/package_map.dart';
|
||||
import 'globals.dart' as globals;
|
||||
import 'ios/bitcode.dart';
|
||||
import 'project.dart';
|
||||
|
||||
/// Builds AOT snapshots given a platform, build mode and a path to a Dart
|
||||
/// library.
|
||||
@ -42,21 +37,6 @@ class AotBuilder {
|
||||
throwToolExit('No AOT build platform specified');
|
||||
}
|
||||
|
||||
if (_canUseAssemble(platform)
|
||||
&& extraGenSnapshotOptions?.isEmpty != false
|
||||
&& extraFrontEndOptions?.isEmpty != false) {
|
||||
await _buildWithAssemble(
|
||||
targetFile: mainDartFile,
|
||||
outputDir: outputPath,
|
||||
targetPlatform: platform,
|
||||
buildMode: buildMode,
|
||||
quiet: quiet,
|
||||
iosArchs: iosBuildArchs ?? defaultIOSArchs,
|
||||
bitcode: bitcode ?? kBitcodeEnabledDefault,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bitcode) {
|
||||
if (platform != TargetPlatform.ios) {
|
||||
throwToolExit('Bitcode is only supported on iOS (TargetPlatform is $platform).');
|
||||
@ -174,81 +154,4 @@ class AotBuilder {
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool _canUseAssemble(TargetPlatform targetPlatform) {
|
||||
switch (targetPlatform) {
|
||||
case TargetPlatform.ios:
|
||||
return true;
|
||||
case TargetPlatform.android_arm:
|
||||
case TargetPlatform.android_arm64:
|
||||
case TargetPlatform.android_x86:
|
||||
case TargetPlatform.darwin_x64:
|
||||
case TargetPlatform.android_x64:
|
||||
case TargetPlatform.linux_x64:
|
||||
case TargetPlatform.windows_x64:
|
||||
case TargetPlatform.fuchsia_arm64:
|
||||
case TargetPlatform.fuchsia_x64:
|
||||
case TargetPlatform.tester:
|
||||
case TargetPlatform.web_javascript:
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _buildWithAssemble({
|
||||
TargetPlatform targetPlatform,
|
||||
BuildMode buildMode,
|
||||
String targetFile,
|
||||
String outputDir,
|
||||
bool quiet,
|
||||
Iterable<DarwinArch> iosArchs,
|
||||
bool bitcode,
|
||||
}) async {
|
||||
Status status;
|
||||
if (!quiet) {
|
||||
final String typeName = globals.artifacts.getEngineType(targetPlatform, buildMode);
|
||||
status = globals.logger.startProgress(
|
||||
'Building AOT snapshot in ${getFriendlyModeName(buildMode)} mode ($typeName)...',
|
||||
timeout: timeoutConfiguration.slowOperation,
|
||||
);
|
||||
}
|
||||
final FlutterProject flutterProject = FlutterProject.current();
|
||||
final Target target = buildMode == BuildMode.profile
|
||||
? const AotAssemblyProfile()
|
||||
: const AotAssemblyRelease();
|
||||
|
||||
final BuildResult result = await buildSystem.build(target, Environment(
|
||||
projectDir: flutterProject.directory,
|
||||
cacheDir: globals.cache.getRoot(),
|
||||
flutterRootDir: globals.fs.directory(Cache.flutterRoot),
|
||||
outputDir: globals.fs.directory(outputDir),
|
||||
buildDir: flutterProject.directory
|
||||
.childDirectory('.dart_tool')
|
||||
.childDirectory('flutter_build'),
|
||||
defines: <String, String>{
|
||||
kBuildMode: getNameForBuildMode(buildMode),
|
||||
kTargetPlatform: getNameForTargetPlatform(targetPlatform),
|
||||
kTargetFile: targetFile,
|
||||
kIosArchs: iosArchs.map(getNameForDarwinArch).join(','),
|
||||
kBitcodeFlag: bitcode.toString()
|
||||
}
|
||||
));
|
||||
status?.stop();
|
||||
if (!result.success) {
|
||||
for (final ExceptionMeasurement measurement in result.exceptions.values) {
|
||||
globals.printError('Target ${measurement.target} failed: ${measurement.exception}',
|
||||
stackTrace: measurement.fatal
|
||||
? measurement.stackTrace
|
||||
: null,
|
||||
);
|
||||
}
|
||||
throwToolExit('Failed to build aot.');
|
||||
}
|
||||
final String builtMessage = 'Built to $outputDir${globals.fs.path.separator}.';
|
||||
if (quiet) {
|
||||
globals.printTrace(builtMessage);
|
||||
} else {
|
||||
globals.printStatus(builtMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -385,6 +385,8 @@ DarwinArch getIOSArchForName(String arch) {
|
||||
return DarwinArch.armv7;
|
||||
case 'arm64':
|
||||
return DarwinArch.arm64;
|
||||
case 'x86_64':
|
||||
return DarwinArch.x86_64;
|
||||
}
|
||||
assert(false);
|
||||
return null;
|
||||
|
@ -11,8 +11,11 @@ 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';
|
||||
|
||||
/// Supports compiling a dart kernel file to an assembly file.
|
||||
@ -25,7 +28,7 @@ abstract class AotAssemblyBase extends Target {
|
||||
@override
|
||||
Future<void> build(Environment environment) async {
|
||||
final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false);
|
||||
final String buildOutputPath = environment.outputDir.path;
|
||||
final String buildOutputPath = environment.buildDir.path;
|
||||
if (environment.defines[kBuildMode] == null) {
|
||||
throw MissingDefineException(kBuildMode, 'aot_assembly');
|
||||
}
|
||||
@ -35,8 +38,11 @@ abstract class AotAssemblyBase extends Target {
|
||||
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];
|
||||
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');
|
||||
}
|
||||
@ -60,7 +66,7 @@ abstract class AotAssemblyBase extends Target {
|
||||
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');
|
||||
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',
|
||||
@ -145,12 +151,215 @@ class AotAssemblyProfile extends AotAssemblyBase {
|
||||
];
|
||||
}
|
||||
|
||||
/// 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'),
|
||||
];
|
||||
|
||||
@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);
|
||||
assetDepfile.writeToFile(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) async {
|
||||
Future<RunResult> createStubAppFramework(File outputFile, SdkType sdk, { bool include32Bit = true }) async {
|
||||
try {
|
||||
outputFile.createSync(recursive: true);
|
||||
} catch (e) {
|
||||
@ -167,8 +376,8 @@ Future<RunResult> createStubAppFramework(File outputFile, SdkType sdk) async {
|
||||
List<String> archFlags;
|
||||
if (sdk == SdkType.iPhone) {
|
||||
archFlags = <String>[
|
||||
'-arch',
|
||||
getNameForDarwinArch(DarwinArch.armv7),
|
||||
if (include32Bit)
|
||||
...<String>['-arch', getNameForDarwinArch(DarwinArch.armv7)],
|
||||
'-arch',
|
||||
getNameForDarwinArch(DarwinArch.arm64),
|
||||
];
|
||||
|
@ -50,6 +50,9 @@ const List<Target> _kDefaultTargets = <Target>[
|
||||
androidArmReleaseBundle,
|
||||
androidArm64ReleaseBundle,
|
||||
androidx64ReleaseBundle,
|
||||
DebugIosApplicationBundle(),
|
||||
ProfileIosApplicationBundle(),
|
||||
ReleaseIosApplicationBundle(),
|
||||
];
|
||||
|
||||
/// Assemble provides a low level API to interact with the flutter tool build
|
||||
|
@ -315,7 +315,7 @@ flutter_tools:lib/''');
|
||||
}));
|
||||
|
||||
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 {
|
||||
globals.fs.file(globals.fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
|
||||
.createSync(recursive: true);
|
||||
@ -360,7 +360,7 @@ flutter_tools:lib/''');
|
||||
}));
|
||||
|
||||
test('aot_assembly_profile with bitcode sends correct argument to snapshotter (mutli arch)', () => testbed.run(() async {
|
||||
iosEnvironment.defines[kIosArchs] = 'armv7,arm64';
|
||||
iosEnvironment.defines[kIosArchs] = 'armv7 arm64';
|
||||
iosEnvironment.defines[kBitcodeFlag] = 'true';
|
||||
|
||||
final FakeProcessResult fakeProcessResult = FakeProcessResult(
|
||||
@ -377,9 +377,8 @@ flutter_tools:lib/''');
|
||||
when(mockXcode.cc(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
when(mockXcode.clang(any)).thenAnswer((_) => Future<RunResult>.value(fakeRunResult));
|
||||
|
||||
final BuildResult result = await buildSystem.build(const AotAssemblyProfile(), iosEnvironment);
|
||||
await const AotAssemblyProfile().build(iosEnvironment);
|
||||
|
||||
expect(result.success, true);
|
||||
verify(mockXcode.cc(argThat(contains('-fembed-bitcode')))).called(2);
|
||||
verify(mockXcode.clang(argThat(contains('-fembed-bitcode')))).called(2);
|
||||
}, overrides: <Type, Generator>{
|
||||
@ -388,7 +387,7 @@ flutter_tools:lib/''');
|
||||
}));
|
||||
|
||||
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 {
|
||||
globals.fs.file(globals.fs.path.join(iosEnvironment.buildDir.path, 'App.framework', 'App'))
|
||||
.createSync(recursive: true);
|
||||
|
@ -0,0 +1,165 @@
|
||||
// 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:file_testing/file_testing.dart';
|
||||
import 'package:flutter_tools/src/artifacts.dart';
|
||||
import 'package:flutter_tools/src/base/file_system.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/targets/dart.dart';
|
||||
import 'package:flutter_tools/src/build_system/targets/ios.dart';
|
||||
import 'package:flutter_tools/src/globals.dart' as globals;
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
||||
import '../../../src/common.dart';
|
||||
import '../../../src/fake_process_manager.dart';
|
||||
import '../../../src/testbed.dart';
|
||||
|
||||
const List<String> _kSharedConfig = <String>[
|
||||
'-dynamiclib',
|
||||
'-fembed-bitcode-marker',
|
||||
'-Xlinker',
|
||||
'-rpath',
|
||||
'-Xlinker',
|
||||
'@executable_path/Frameworks',
|
||||
'-Xlinker',
|
||||
'-rpath',
|
||||
'-Xlinker',
|
||||
'@loader_path/Frameworks',
|
||||
'-install_name',
|
||||
'@rpath/App.framework/App',
|
||||
'-isysroot',
|
||||
];
|
||||
|
||||
void main() {
|
||||
Testbed testbed;
|
||||
Environment environment;
|
||||
ProcessManager processManager;
|
||||
|
||||
setUp(() {
|
||||
testbed = Testbed(setup: () {
|
||||
environment = Environment.test(globals.fs.currentDirectory, defines: <String, String>{
|
||||
kTargetPlatform: 'ios',
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
test('DebugUniveralFramework creates expected binary with arm64 only arch', () => testbed.run(() async {
|
||||
environment.defines[kIosArchs] = 'arm64';
|
||||
processManager = FakeProcessManager.list(<FakeCommand>[
|
||||
// Create iphone stub.
|
||||
const FakeCommand(command: <String>['xcrun', '--sdk', 'iphoneos', '--show-sdk-path']),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'clang',
|
||||
'-x',
|
||||
'c',
|
||||
// iphone only gets 64 bit arch based on kIosArchs
|
||||
'-arch',
|
||||
'arm64',
|
||||
globals.fs.path.absolute(globals.fs.path.join('.tmp_rand0', 'flutter_tools_stub_source.rand0', 'debug_app.cc')),
|
||||
..._kSharedConfig,
|
||||
'',
|
||||
'-o',
|
||||
environment.buildDir.childFile('iphone_framework').path
|
||||
]),
|
||||
// Create simulator stub.
|
||||
const FakeCommand(command: <String>['xcrun', '--sdk', 'iphonesimulator', '--show-sdk-path']),
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'clang',
|
||||
'-x',
|
||||
'c',
|
||||
// Simulator only as x86_64 arch
|
||||
'-arch',
|
||||
'x86_64',
|
||||
globals.fs.path.absolute(globals.fs.path.join('.tmp_rand0', 'flutter_tools_stub_source.rand0', 'debug_app.cc')),
|
||||
..._kSharedConfig,
|
||||
'',
|
||||
'-o',
|
||||
environment.buildDir.childFile('simulator_framework').path
|
||||
]),
|
||||
// Lipo stubs together.
|
||||
FakeCommand(command: <String>[
|
||||
'xcrun',
|
||||
'lipo',
|
||||
'-create',
|
||||
environment.buildDir.childFile('iphone_framework').path,
|
||||
environment.buildDir.childFile('simulator_framework').path,
|
||||
'-output',
|
||||
environment.buildDir.childFile('App').path,
|
||||
]),
|
||||
]);
|
||||
|
||||
await const DebugUniveralFramework().build(environment);
|
||||
}, overrides: <Type, Generator>{
|
||||
ProcessManager: () => processManager,
|
||||
}));
|
||||
|
||||
test('DebugIosApplicationBundle', () => testbed.run(() async {
|
||||
environment.defines[kBuildMode] = 'debug';
|
||||
// Precompiled dart data
|
||||
when(globals.artifacts.getArtifactPath(Artifact.vmSnapshotData, mode: BuildMode.debug))
|
||||
.thenReturn('vm_snapshot_data');
|
||||
when(globals.artifacts.getArtifactPath(Artifact.isolateSnapshotData, mode: BuildMode.debug))
|
||||
.thenReturn('isolate_snapshot_data');
|
||||
globals.fs.file('vm_snapshot_data').createSync();
|
||||
globals.fs.file('isolate_snapshot_data').createSync();
|
||||
// Project info
|
||||
globals.fs.file('pubspec.yaml').writeAsStringSync('name: hello');
|
||||
globals.fs.file('.packages').writeAsStringSync('\n');
|
||||
// Plist file
|
||||
globals.fs.file(globals.fs.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
|
||||
..createSync(recursive: true);
|
||||
// App kernel
|
||||
environment.buildDir.childFile('app.dill').createSync(recursive: true);
|
||||
// Stub framework
|
||||
environment.buildDir.childFile('App').createSync();
|
||||
|
||||
await const DebugIosApplicationBundle().build(environment);
|
||||
|
||||
final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
|
||||
expect(frameworkDirectory.childFile('App'), exists);
|
||||
expect(frameworkDirectory.childFile('Info.plist'), exists);
|
||||
|
||||
final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
|
||||
expect(assetDirectory.childFile('kernel_blob.bin'), exists);
|
||||
expect(assetDirectory.childFile('AssetManifest.json'), exists);
|
||||
expect(assetDirectory.childFile('vm_snapshot_data'), exists);
|
||||
expect(assetDirectory.childFile('isolate_snapshot_data'), exists);
|
||||
}, overrides: <Type, Generator>{
|
||||
Artifacts: () => MockArtifacts(),
|
||||
}));
|
||||
|
||||
test('ReleaseIosApplicationBundle', () => testbed.run(() async {
|
||||
environment.defines[kBuildMode] = 'release';
|
||||
|
||||
// Project info
|
||||
globals.fs.file('pubspec.yaml').writeAsStringSync('name: hello');
|
||||
globals.fs.file('.packages').writeAsStringSync('\n');
|
||||
// Plist file
|
||||
globals.fs.file(globals.fs.path.join('ios', 'Flutter', 'AppFrameworkInfo.plist'))
|
||||
..createSync(recursive: true);
|
||||
|
||||
// Real framework
|
||||
environment.buildDir
|
||||
.childDirectory('App.framework')
|
||||
.childFile('App')
|
||||
.createSync(recursive: true);
|
||||
|
||||
await const ReleaseIosApplicationBundle().build(environment);
|
||||
|
||||
final Directory frameworkDirectory = environment.outputDir.childDirectory('App.framework');
|
||||
expect(frameworkDirectory.childFile('App'), exists);
|
||||
expect(frameworkDirectory.childFile('Info.plist'), exists);
|
||||
|
||||
final Directory assetDirectory = frameworkDirectory.childDirectory('flutter_assets');
|
||||
expect(assetDirectory.childFile('kernel_blob.bin'), isNot(exists));
|
||||
expect(assetDirectory.childFile('AssetManifest.json'), exists);
|
||||
expect(assetDirectory.childFile('vm_snapshot_data'), isNot(exists));
|
||||
expect(assetDirectory.childFile('isolate_snapshot_data'), isNot(exists));
|
||||
}));
|
||||
}
|
||||
|
||||
class MockArtifacts extends Mock implements Artifacts {}
|
Loading…
x
Reference in New Issue
Block a user