Build ios framework (#44065)
This commit is contained in:
parent
028ed7122a
commit
d2e87a5d2c
@ -761,6 +761,7 @@ Future<void> _runHostOnlyDeviceLabTests() async {
|
||||
|
||||
// TODO(jmagman): Re-enable once flakiness is resolved, https://github.com/flutter/flutter/issues/37525
|
||||
// if (Platform.isMacOS) () => _runDevicelabTest('module_test_ios'),
|
||||
if (Platform.isMacOS) () => _runDevicelabTest('build_ios_framework_module_test'),
|
||||
if (Platform.isMacOS) () => _runDevicelabTest('plugin_lint_mac'),
|
||||
() => _runDevicelabTest('plugin_test', environment: gradleEnvironment),
|
||||
]..shuffle(math.Random(0));
|
||||
|
143
dev/devicelab/bin/tasks/build_ios_framework_module_test.dart
Normal file
143
dev/devicelab/bin/tasks/build_ios_framework_module_test.dart
Normal file
@ -0,0 +1,143 @@
|
||||
// Copyright (c) 2019 The Chromium 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 'dart:async';
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:flutter_devicelab/framework/apk_utils.dart';
|
||||
import 'package:flutter_devicelab/framework/framework.dart';
|
||||
import 'package:flutter_devicelab/framework/utils.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
|
||||
/// Tests that iOS .frameworks can be built on module projects.
|
||||
Future<void> main() async {
|
||||
await task(() async {
|
||||
|
||||
section('Create module project');
|
||||
|
||||
final Directory tempDir = Directory.systemTemp.createTempSync('flutter_module_test.');
|
||||
final Directory projectDir = Directory(path.join(tempDir.path, 'hello'));
|
||||
try {
|
||||
await inDirectory(tempDir, () async {
|
||||
await flutter(
|
||||
'create',
|
||||
options: <String>['--org', 'io.flutter.devicelab', '--template', 'module', 'hello'],
|
||||
);
|
||||
});
|
||||
|
||||
section('Add plugins');
|
||||
|
||||
final File pubspec = File(path.join(projectDir.path, 'pubspec.yaml'));
|
||||
String content = pubspec.readAsStringSync();
|
||||
content = content.replaceFirst(
|
||||
'\ndependencies:\n',
|
||||
'\ndependencies:\n device_info: 0.4.1\n package_info: 0.4.0+9\n',
|
||||
);
|
||||
pubspec.writeAsStringSync(content, flush: true);
|
||||
await inDirectory(projectDir, () async {
|
||||
await flutter(
|
||||
'packages',
|
||||
options: <String>['get'],
|
||||
);
|
||||
});
|
||||
|
||||
// This builds all build modes' frameworks by default
|
||||
section('Build frameworks');
|
||||
|
||||
await inDirectory(projectDir, () async {
|
||||
await flutter(
|
||||
'build',
|
||||
options: <String>['ios-framework'],
|
||||
);
|
||||
});
|
||||
|
||||
final String outputPath = path.join(
|
||||
projectDir.path,
|
||||
'build',
|
||||
'ios',
|
||||
'framework',
|
||||
);
|
||||
|
||||
section('Check debug build has Dart snapshot as asset');
|
||||
|
||||
checkFileExists(path.join(
|
||||
outputPath,
|
||||
'Debug',
|
||||
'App.framework',
|
||||
'flutter_assets',
|
||||
'vm_snapshot_data',
|
||||
));
|
||||
|
||||
section('Check profile, release builds has Dart dylib');
|
||||
|
||||
for (String mode in <String>['Profile', 'Release']) {
|
||||
checkFileExists(path.join(
|
||||
outputPath,
|
||||
mode,
|
||||
'App.framework',
|
||||
'App',
|
||||
));
|
||||
|
||||
checkFileNotExists(path.join(
|
||||
outputPath,
|
||||
mode,
|
||||
'App.framework',
|
||||
'flutter_assets',
|
||||
'vm_snapshot_data',
|
||||
));
|
||||
}
|
||||
|
||||
section("Check all modes' engine dylib");
|
||||
|
||||
for (String mode in <String>['Debug', 'Profile', 'Release']) {
|
||||
checkFileExists(path.join(
|
||||
outputPath,
|
||||
mode,
|
||||
'Flutter.framework',
|
||||
'Flutter',
|
||||
));
|
||||
}
|
||||
|
||||
section("Check all modes' engine header");
|
||||
|
||||
for (String mode in <String>['Debug', 'Profile', 'Release']) {
|
||||
checkFileContains(
|
||||
<String>['#include "FlutterEngine.h"'],
|
||||
path.join(outputPath, mode, 'Flutter.framework', 'Headers', 'Flutter.h'),
|
||||
);
|
||||
}
|
||||
|
||||
section("Check all modes' have plugin dylib");
|
||||
|
||||
for (String mode in <String>['Debug', 'Profile', 'Release']) {
|
||||
checkFileExists(path.join(
|
||||
outputPath,
|
||||
mode,
|
||||
'device_info.framework',
|
||||
'device_info',
|
||||
));
|
||||
}
|
||||
|
||||
section("Check all modes' have generated plugin registrant");
|
||||
|
||||
for (String mode in <String>['Debug', 'Profile', 'Release']) {
|
||||
checkFileExists(path.join(
|
||||
outputPath,
|
||||
mode,
|
||||
'FlutterPluginRegistrant.framework',
|
||||
'Headers',
|
||||
'GeneratedPluginRegistrant.h',
|
||||
));
|
||||
}
|
||||
|
||||
return TaskResult.success(null);
|
||||
} on TaskResult catch (taskResult) {
|
||||
return taskResult;
|
||||
} catch (e) {
|
||||
return TaskResult.failure(e.toString());
|
||||
} finally {
|
||||
rmTree(tempDir);
|
||||
}
|
||||
});
|
||||
}
|
@ -625,6 +625,13 @@ void checkFileExists(String file) {
|
||||
}
|
||||
}
|
||||
|
||||
/// Checks that the file does not exists, otherwise throws a [FileSystemException].
|
||||
void checkFileNotExists(String file) {
|
||||
if (exists(File(file))) {
|
||||
throw FileSystemException('Expected file to exit.', file);
|
||||
}
|
||||
}
|
||||
|
||||
void _checkExitCode(int code) {
|
||||
if (code != 0) {
|
||||
throw Exception(
|
||||
|
239
packages/flutter_tools/lib/src/aot.dart
Normal file
239
packages/flutter_tools/lib/src/aot.dart
Normal file
@ -0,0 +1,239 @@
|
||||
// Copyright 2019 The Chromium 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 'dart:async';
|
||||
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
import 'base/build.dart';
|
||||
import 'base/common.dart';
|
||||
import 'base/file_system.dart';
|
||||
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 'dart/package_map.dart';
|
||||
import 'globals.dart';
|
||||
import 'ios/bitcode.dart';
|
||||
import 'project.dart';
|
||||
|
||||
/// Builds AOT snapshots given a platform, build mode and a path to a Dart
|
||||
/// library.
|
||||
class AotBuilder {
|
||||
Future<void> build({
|
||||
@required TargetPlatform platform,
|
||||
@required String outputPath,
|
||||
@required BuildMode buildMode,
|
||||
@required String mainDartFile,
|
||||
bool bitcode = kBitcodeEnabledDefault,
|
||||
bool quiet = true,
|
||||
bool reportTimings = false,
|
||||
Iterable<DarwinArch> iosBuildArchs = defaultIOSArchs,
|
||||
List<String> extraFrontEndOptions,
|
||||
List<String> extraGenSnapshotOptions,
|
||||
}) async {
|
||||
if (platform == null) {
|
||||
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,
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bitcode) {
|
||||
if (platform != TargetPlatform.ios) {
|
||||
throwToolExit('Bitcode is only supported on iOS (TargetPlatform is $platform).');
|
||||
}
|
||||
await validateBitcode(buildMode, platform);
|
||||
}
|
||||
|
||||
Status status;
|
||||
if (!quiet) {
|
||||
final String typeName = artifacts.getEngineType(platform, buildMode);
|
||||
status = logger.startProgress(
|
||||
'Building AOT snapshot in ${getFriendlyModeName(buildMode)} mode ($typeName)...',
|
||||
timeout: timeoutConfiguration.slowOperation,
|
||||
);
|
||||
}
|
||||
try {
|
||||
final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: reportTimings);
|
||||
|
||||
// Compile to kernel.
|
||||
final String kernelOut = await snapshotter.compileKernel(
|
||||
platform: platform,
|
||||
buildMode: buildMode,
|
||||
mainPath: mainDartFile,
|
||||
packagesPath: PackageMap.globalPackagesPath,
|
||||
trackWidgetCreation: false,
|
||||
outputPath: outputPath,
|
||||
extraFrontEndOptions: extraFrontEndOptions,
|
||||
);
|
||||
if (kernelOut == null) {
|
||||
throwToolExit('Compiler terminated unexpectedly.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Build AOT snapshot.
|
||||
if (platform == TargetPlatform.ios) {
|
||||
// Determine which iOS architectures to build for.
|
||||
final Map<DarwinArch, String> iosBuilds = <DarwinArch, String>{};
|
||||
for (DarwinArch arch in iosBuildArchs) {
|
||||
iosBuilds[arch] = fs.path.join(outputPath, getNameForDarwinArch(arch));
|
||||
}
|
||||
|
||||
// Generate AOT snapshot and compile to arch-specific App.framework.
|
||||
final Map<DarwinArch, Future<int>> exitCodes = <DarwinArch, Future<int>>{};
|
||||
iosBuilds.forEach((DarwinArch iosArch, String outputPath) {
|
||||
exitCodes[iosArch] = snapshotter.build(
|
||||
platform: platform,
|
||||
darwinArch: iosArch,
|
||||
buildMode: buildMode,
|
||||
mainPath: kernelOut,
|
||||
packagesPath: PackageMap.globalPackagesPath,
|
||||
outputPath: outputPath,
|
||||
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
||||
bitcode: bitcode,
|
||||
quiet: quiet,
|
||||
).then<int>((int buildExitCode) {
|
||||
return buildExitCode;
|
||||
});
|
||||
});
|
||||
|
||||
// Merge arch-specific App.frameworks into a multi-arch App.framework.
|
||||
if ((await Future.wait<int>(exitCodes.values)).every((int buildExitCode) => buildExitCode == 0)) {
|
||||
final Iterable<String> dylibs = iosBuilds.values.map<String>(
|
||||
(String outputDir) => fs.path.join(outputDir, 'App.framework', 'App'));
|
||||
fs.directory(fs.path.join(outputPath, 'App.framework'))..createSync();
|
||||
await processUtils.run(
|
||||
<String>[
|
||||
'lipo',
|
||||
...dylibs,
|
||||
'-create',
|
||||
'-output', fs.path.join(outputPath, 'App.framework', 'App'),
|
||||
],
|
||||
throwOnError: true,
|
||||
);
|
||||
} else {
|
||||
status?.cancel();
|
||||
exitCodes.forEach((DarwinArch iosArch, Future<int> exitCodeFuture) async {
|
||||
final int buildExitCode = await exitCodeFuture;
|
||||
printError('Snapshotting ($iosArch) exited with non-zero exit code: $buildExitCode');
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Android AOT snapshot.
|
||||
final int snapshotExitCode = await snapshotter.build(
|
||||
platform: platform,
|
||||
buildMode: buildMode,
|
||||
mainPath: kernelOut,
|
||||
packagesPath: PackageMap.globalPackagesPath,
|
||||
outputPath: outputPath,
|
||||
extraGenSnapshotOptions: extraGenSnapshotOptions,
|
||||
bitcode: false,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
status?.cancel();
|
||||
throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
|
||||
}
|
||||
}
|
||||
} on ProcessException catch (error) {
|
||||
// Catch the String exceptions thrown from the `runSync` methods below.
|
||||
status?.cancel();
|
||||
printError(error.toString());
|
||||
return;
|
||||
}
|
||||
status?.stop();
|
||||
|
||||
if (outputPath == null) {
|
||||
throwToolExit(null);
|
||||
}
|
||||
|
||||
final String builtMessage = 'Built to $outputPath${fs.path.separator}.';
|
||||
if (quiet) {
|
||||
printTrace(builtMessage);
|
||||
} else {
|
||||
printStatus(builtMessage);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
bool _canUseAssemble(TargetPlatform targetPlatform) {
|
||||
switch (targetPlatform) {
|
||||
case TargetPlatform.android_arm:
|
||||
case TargetPlatform.android_arm64:
|
||||
case TargetPlatform.android_x86:
|
||||
case TargetPlatform.darwin_x64:
|
||||
return true;
|
||||
case TargetPlatform.android_x64:
|
||||
case TargetPlatform.ios:
|
||||
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
|
||||
}) async {
|
||||
Status status;
|
||||
if (!quiet) {
|
||||
final String typeName = artifacts.getEngineType(targetPlatform, buildMode);
|
||||
status = logger.startProgress(
|
||||
'Building AOT snapshot in ${getFriendlyModeName(buildMode)} mode ($typeName)...',
|
||||
timeout: timeoutConfiguration.slowOperation,
|
||||
);
|
||||
}
|
||||
final FlutterProject flutterProject = FlutterProject.current();
|
||||
// Currently this only supports android, per the check above.
|
||||
final Target target = buildMode == BuildMode.profile
|
||||
? const ProfileCopyFlutterAotBundle()
|
||||
: const ReleaseCopyFlutterAotBundle();
|
||||
|
||||
final BuildResult result = await buildSystem.build(target, Environment(
|
||||
projectDir: flutterProject.directory,
|
||||
outputDir: fs.directory(outputDir),
|
||||
buildDir: flutterProject.directory
|
||||
.childDirectory('.dart_tool')
|
||||
.childDirectory('flutter_build'),
|
||||
defines: <String, String>{
|
||||
kBuildMode: getNameForBuildMode(buildMode),
|
||||
kTargetPlatform: getNameForTargetPlatform(targetPlatform),
|
||||
kTargetFile: targetFile,
|
||||
}
|
||||
));
|
||||
status?.stop();
|
||||
if (!result.success) {
|
||||
for (ExceptionMeasurement measurement in result.exceptions.values) {
|
||||
printError(measurement.exception.toString());
|
||||
printError(measurement.stackTrace.toString());
|
||||
}
|
||||
throwToolExit('Failed to build aot.');
|
||||
}
|
||||
final String builtMessage = 'Built to $outputDir${fs.path.separator}.';
|
||||
if (quiet) {
|
||||
printTrace(builtMessage);
|
||||
} else {
|
||||
printStatus(builtMessage);
|
||||
}
|
||||
}
|
||||
}
|
@ -94,6 +94,7 @@ class AOTSnapshotter {
|
||||
DarwinArch darwinArch,
|
||||
List<String> extraGenSnapshotOptions = const <String>[],
|
||||
@required bool bitcode,
|
||||
bool quiet = false,
|
||||
}) async {
|
||||
if (bitcode && platform != TargetPlatform.ios) {
|
||||
printError('Bitcode is only supported for iOS.');
|
||||
@ -208,6 +209,7 @@ class AOTSnapshotter {
|
||||
assemblyPath: stripSymbols ? '$assembly.stripped.S' : assembly,
|
||||
outputPath: outputDir.path,
|
||||
bitcode: bitcode,
|
||||
quiet: quiet,
|
||||
);
|
||||
if (result.exitCode != 0) {
|
||||
return result.exitCode;
|
||||
@ -224,9 +226,12 @@ class AOTSnapshotter {
|
||||
@required String assemblyPath,
|
||||
@required String outputPath,
|
||||
@required bool bitcode,
|
||||
@required bool quiet
|
||||
}) async {
|
||||
final String targetArch = getNameForDarwinArch(appleArch);
|
||||
printStatus('Building App.framework for $targetArch...');
|
||||
if (!quiet) {
|
||||
printStatus('Building App.framework for $targetArch...');
|
||||
}
|
||||
|
||||
final List<String> commonBuildOptions = <String>[
|
||||
'-arch', targetArch,
|
||||
|
@ -173,7 +173,7 @@ bool isOlderThanReference({ @required FileSystemEntity entity, @required File re
|
||||
return true;
|
||||
}
|
||||
return referenceFile.existsSync()
|
||||
&& referenceFile.lastModifiedSync().isAfter(entity.statSync().modified);
|
||||
&& referenceFile.statSync().modified.isAfter(entity.statSync().modified);
|
||||
}
|
||||
|
||||
/// Exception indicating that a file that was expected to exist was not found.
|
||||
|
@ -4,10 +4,13 @@
|
||||
|
||||
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 '../../base/process_manager.dart';
|
||||
import '../../build_info.dart';
|
||||
import '../../macos/xcode.dart';
|
||||
import '../build_system.dart';
|
||||
import '../exceptions.dart';
|
||||
import 'dart.dart';
|
||||
@ -22,7 +25,7 @@ abstract class AotAssemblyBase extends Target {
|
||||
@override
|
||||
Future<void> build(Environment environment) async {
|
||||
final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: false);
|
||||
final String outputPath = environment.buildDir.path;
|
||||
final String buildOutputPath = environment.buildDir.path;
|
||||
if (environment.defines[kBuildMode] == null) {
|
||||
throw MissingDefineException(kBuildMode, 'aot_assembly');
|
||||
}
|
||||
@ -45,7 +48,7 @@ abstract class AotAssemblyBase extends Target {
|
||||
buildMode: buildMode,
|
||||
mainPath: environment.buildDir.childFile('app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: outputPath,
|
||||
outputPath: environment.outputDir.path,
|
||||
darwinArch: iosArchs.single,
|
||||
bitcode: bitcode,
|
||||
);
|
||||
@ -62,7 +65,7 @@ abstract class AotAssemblyBase extends Target {
|
||||
buildMode: buildMode,
|
||||
mainPath: environment.buildDir.childFile('app.dill').path,
|
||||
packagesPath: environment.projectDir.childFile('.packages').path,
|
||||
outputPath: fs.path.join(outputPath, getNameForDarwinArch(iosArch)),
|
||||
outputPath: fs.path.join(buildOutputPath, getNameForDarwinArch(iosArch)),
|
||||
darwinArch: iosArch,
|
||||
bitcode: bitcode,
|
||||
));
|
||||
@ -74,10 +77,10 @@ abstract class AotAssemblyBase extends Target {
|
||||
final ProcessResult result = await processManager.run(<String>[
|
||||
'lipo',
|
||||
...iosArchs.map((DarwinArch iosArch) =>
|
||||
fs.path.join(outputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')),
|
||||
fs.path.join(buildOutputPath, getNameForDarwinArch(iosArch), 'App.framework', 'App')),
|
||||
'-create',
|
||||
'-output',
|
||||
fs.path.join(outputPath, 'App.framework', 'App'),
|
||||
fs.path.join(environment.outputDir.path, 'App.framework', 'App'),
|
||||
]);
|
||||
if (result.exitCode != 0) {
|
||||
throw Exception('lipo exited with code ${result.exitCode}');
|
||||
@ -108,7 +111,7 @@ class AotAssemblyRelease extends AotAssemblyBase {
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/App.framework/App'),
|
||||
Source.pattern('{OUTPUT_DIR}/App.framework/App'),
|
||||
];
|
||||
|
||||
@override
|
||||
@ -140,7 +143,7 @@ class AotAssemblyProfile extends AotAssemblyBase {
|
||||
|
||||
@override
|
||||
List<Source> get outputs => const <Source>[
|
||||
Source.pattern('{BUILD_DIR}/App.framework/App'),
|
||||
Source.pattern('{OUTPUT_DIR}/App.framework/App'),
|
||||
];
|
||||
|
||||
@override
|
||||
@ -148,3 +151,47 @@ class AotAssemblyProfile extends AotAssemblyBase {
|
||||
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(Directory appFrameworkDirectory) async {
|
||||
File outputFile;
|
||||
try {
|
||||
if (!appFrameworkDirectory.existsSync()) {
|
||||
appFrameworkDirectory.createSync(recursive: true);
|
||||
}
|
||||
|
||||
outputFile = appFrameworkDirectory.childFile('App');
|
||||
outputFile.createSync(recursive: true);
|
||||
} catch (e) {
|
||||
throwToolExit('Failed to create App.framework stub at ${appFrameworkDirectory.path}');
|
||||
}
|
||||
|
||||
final Directory tempDir = fs.systemTempDirectory.createTempSync('flutter_tools_stub_source.');
|
||||
try {
|
||||
final File stubSource = tempDir.childFile('debug_app.cc')
|
||||
..writeAsStringSync(r'''
|
||||
static const int Moo = 88;
|
||||
''');
|
||||
|
||||
return await xcode.clang(<String>[
|
||||
'-x',
|
||||
'c',
|
||||
stubSource.path,
|
||||
'-dynamiclib',
|
||||
'-Xlinker', '-rpath', '-Xlinker', '@executable_path/Frameworks',
|
||||
'-Xlinker', '-rpath', '-Xlinker', '@loader_path/Frameworks',
|
||||
'-install_name', '@rpath/App.framework/App',
|
||||
'-o', outputFile.path,
|
||||
]);
|
||||
} finally {
|
||||
try {
|
||||
tempDir.deleteSync(recursive: true);
|
||||
} on FileSystemException catch (_) {
|
||||
// Best effort. Sometimes we can't delete things from system temp.
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -16,6 +16,7 @@ import 'build_appbundle.dart';
|
||||
import 'build_bundle.dart';
|
||||
import 'build_fuchsia.dart';
|
||||
import 'build_ios.dart';
|
||||
import 'build_ios_framework.dart';
|
||||
import 'build_web.dart';
|
||||
|
||||
class BuildCommand extends FlutterCommand {
|
||||
@ -25,6 +26,7 @@ class BuildCommand extends FlutterCommand {
|
||||
addSubcommand(BuildAppBundleCommand(verboseHelp: verboseHelp));
|
||||
addSubcommand(BuildAotCommand(verboseHelp: verboseHelp));
|
||||
addSubcommand(BuildIOSCommand());
|
||||
addSubcommand(BuildIOSFrameworkCommand());
|
||||
addSubcommand(BuildBundleCommand(verboseHelp: verboseHelp));
|
||||
addSubcommand(BuildWebCommand());
|
||||
addSubcommand(BuildMacosCommand(verboseHelp: verboseHelp));
|
||||
|
@ -36,13 +36,13 @@ class BuildAarCommand extends BuildSubCommand {
|
||||
@override
|
||||
Future<Map<CustomDimensions, String>> get usageValues async {
|
||||
final Map<CustomDimensions, String> usage = <CustomDimensions, String>{};
|
||||
final FlutterProject futterProject = _getProject();
|
||||
if (futterProject == null) {
|
||||
final FlutterProject flutterProject = _getProject();
|
||||
if (flutterProject == null) {
|
||||
return usage;
|
||||
}
|
||||
if (futterProject.manifest.isModule) {
|
||||
if (flutterProject.manifest.isModule) {
|
||||
usage[CustomDimensions.commandBuildAarProjectType] = 'module';
|
||||
} else if (futterProject.manifest.isPlugin) {
|
||||
} else if (flutterProject.manifest.isPlugin) {
|
||||
usage[CustomDimensions.commandBuildAarProjectType] = 'plugin';
|
||||
} else {
|
||||
usage[CustomDimensions.commandBuildAarProjectType] = 'app';
|
||||
|
@ -4,29 +4,17 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import '../artifacts.dart';
|
||||
import '../base/build.dart';
|
||||
import '../aot.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/io.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/version.dart';
|
||||
import '../build_info.dart';
|
||||
import '../build_system/build_system.dart';
|
||||
import '../build_system/targets/dart.dart';
|
||||
import '../dart/package_map.dart';
|
||||
import '../globals.dart';
|
||||
import '../ios/plist_parser.dart';
|
||||
import '../macos/xcode.dart';
|
||||
import '../project.dart';
|
||||
import '../ios/bitcode.dart';
|
||||
import '../resident_runner.dart';
|
||||
import '../runner/flutter_command.dart';
|
||||
import 'build.dart';
|
||||
|
||||
/// Builds AOT snapshots into platform specific library containers.
|
||||
class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmentArtifacts {
|
||||
BuildAotCommand({bool verboseHelp = false}) {
|
||||
BuildAotCommand({bool verboseHelp = false, this.aotBuilder}) {
|
||||
usesTargetOption();
|
||||
addBuildModeFlags();
|
||||
usesPubOption();
|
||||
@ -57,7 +45,7 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
|
||||
hide: true,
|
||||
)
|
||||
..addFlag('bitcode',
|
||||
defaultsTo: false,
|
||||
defaultsTo: kBitcodeEnabledDefault,
|
||||
help: 'Build the AOT bundle with bitcode. Requires a compatible bitcode engine.',
|
||||
hide: true,
|
||||
);
|
||||
@ -67,6 +55,8 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
|
||||
usesTrackWidgetCreation(hasEffect: false, verboseHelp: verboseHelp);
|
||||
}
|
||||
|
||||
AotBuilder aotBuilder;
|
||||
|
||||
@override
|
||||
final String name = 'aot';
|
||||
|
||||
@ -82,258 +72,21 @@ class BuildAotCommand extends BuildSubCommand with TargetPlatformBasedDevelopmen
|
||||
if (platform == null) {
|
||||
throwToolExit('Unknown platform: $targetPlatform');
|
||||
}
|
||||
if (_canUseAssemble(platform)) {
|
||||
await _buildWithAssemble(
|
||||
targetFile: findMainDartFile(targetFile),
|
||||
outputDir: outputPath,
|
||||
targetPlatform: platform,
|
||||
buildMode: buildMode,
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
final bool bitcode = argResults['bitcode'];
|
||||
aotBuilder ??= AotBuilder();
|
||||
|
||||
if (bitcode) {
|
||||
if (platform != TargetPlatform.ios) {
|
||||
throwToolExit('Bitcode is only supported on iOS (TargetPlatform is $targetPlatform).');
|
||||
}
|
||||
await validateBitcode(buildMode, platform);
|
||||
}
|
||||
|
||||
Status status;
|
||||
if (!argResults['quiet']) {
|
||||
final String typeName = artifacts.getEngineType(platform, buildMode);
|
||||
status = logger.startProgress(
|
||||
'Building AOT snapshot in ${getFriendlyModeName(getBuildMode())} mode ($typeName)...',
|
||||
timeout: timeoutConfiguration.slowOperation,
|
||||
);
|
||||
}
|
||||
final bool reportTimings = argResults['report-timings'];
|
||||
try {
|
||||
String mainPath = findMainDartFile(targetFile);
|
||||
final AOTSnapshotter snapshotter = AOTSnapshotter(reportTimings: reportTimings);
|
||||
|
||||
// Compile to kernel.
|
||||
mainPath = await snapshotter.compileKernel(
|
||||
platform: platform,
|
||||
buildMode: buildMode,
|
||||
mainPath: mainPath,
|
||||
packagesPath: PackageMap.globalPackagesPath,
|
||||
trackWidgetCreation: false,
|
||||
outputPath: outputPath,
|
||||
extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
|
||||
);
|
||||
if (mainPath == null) {
|
||||
throwToolExit('Compiler terminated unexpectedly.');
|
||||
return null;
|
||||
}
|
||||
|
||||
// Build AOT snapshot.
|
||||
if (platform == TargetPlatform.ios) {
|
||||
// Determine which iOS architectures to build for.
|
||||
final Iterable<DarwinArch> buildArchs = argResults['ios-arch'].map<DarwinArch>(getIOSArchForName);
|
||||
final Map<DarwinArch, String> iosBuilds = <DarwinArch, String>{};
|
||||
for (DarwinArch arch in buildArchs) {
|
||||
iosBuilds[arch] = fs.path.join(outputPath, getNameForDarwinArch(arch));
|
||||
}
|
||||
|
||||
// Generate AOT snapshot and compile to arch-specific App.framework.
|
||||
final Map<DarwinArch, Future<int>> exitCodes = <DarwinArch, Future<int>>{};
|
||||
iosBuilds.forEach((DarwinArch iosArch, String outputPath) {
|
||||
exitCodes[iosArch] = snapshotter.build(
|
||||
platform: platform,
|
||||
darwinArch: iosArch,
|
||||
buildMode: buildMode,
|
||||
mainPath: mainPath,
|
||||
packagesPath: PackageMap.globalPackagesPath,
|
||||
outputPath: outputPath,
|
||||
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
|
||||
bitcode: bitcode,
|
||||
).then<int>((int buildExitCode) {
|
||||
return buildExitCode;
|
||||
});
|
||||
});
|
||||
|
||||
// Merge arch-specific App.frameworks into a multi-arch App.framework.
|
||||
if ((await Future.wait<int>(exitCodes.values)).every((int buildExitCode) => buildExitCode == 0)) {
|
||||
final Iterable<String> dylibs = iosBuilds.values.map<String>(
|
||||
(String outputDir) => fs.path.join(outputDir, 'App.framework', 'App'));
|
||||
fs.directory(fs.path.join(outputPath, 'App.framework'))..createSync();
|
||||
await processUtils.run(
|
||||
<String>[
|
||||
'lipo',
|
||||
...dylibs,
|
||||
'-create',
|
||||
'-output', fs.path.join(outputPath, 'App.framework', 'App'),
|
||||
],
|
||||
throwOnError: true,
|
||||
);
|
||||
} else {
|
||||
status?.cancel();
|
||||
exitCodes.forEach((DarwinArch iosArch, Future<int> exitCodeFuture) async {
|
||||
final int buildExitCode = await exitCodeFuture;
|
||||
printError('Snapshotting ($iosArch) exited with non-zero exit code: $buildExitCode');
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Android AOT snapshot.
|
||||
final int snapshotExitCode = await snapshotter.build(
|
||||
platform: platform,
|
||||
buildMode: buildMode,
|
||||
mainPath: mainPath,
|
||||
packagesPath: PackageMap.globalPackagesPath,
|
||||
outputPath: outputPath,
|
||||
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
|
||||
bitcode: false,
|
||||
);
|
||||
if (snapshotExitCode != 0) {
|
||||
status?.cancel();
|
||||
throwToolExit('Snapshotting exited with non-zero exit code: $snapshotExitCode');
|
||||
}
|
||||
}
|
||||
} on ProcessException catch (error) {
|
||||
// Catch the String exceptions thrown from the `runSync` methods below.
|
||||
status?.cancel();
|
||||
printError(error.toString());
|
||||
return null;
|
||||
}
|
||||
status?.stop();
|
||||
|
||||
if (outputPath == null) {
|
||||
throwToolExit(null);
|
||||
}
|
||||
|
||||
final String builtMessage = 'Built to $outputPath${fs.path.separator}.';
|
||||
if (argResults['quiet']) {
|
||||
printTrace(builtMessage);
|
||||
} else {
|
||||
printStatus(builtMessage);
|
||||
}
|
||||
await aotBuilder.build(
|
||||
platform: platform,
|
||||
outputPath: outputPath,
|
||||
buildMode: buildMode,
|
||||
mainDartFile: findMainDartFile(targetFile),
|
||||
bitcode: argResults['bitcode'],
|
||||
quiet: argResults['quiet'],
|
||||
reportTimings: argResults['report-timings'],
|
||||
iosBuildArchs: argResults['ios-arch'].map<DarwinArch>(getIOSArchForName),
|
||||
extraFrontEndOptions: argResults[FlutterOptions.kExtraFrontEndOptions],
|
||||
extraGenSnapshotOptions: argResults[FlutterOptions.kExtraGenSnapshotOptions],
|
||||
);
|
||||
return null;
|
||||
}
|
||||
|
||||
bool _canUseAssemble(TargetPlatform targetPlatform) {
|
||||
if (argResults.wasParsed(FlutterOptions.kExtraFrontEndOptions) ||
|
||||
argResults.wasParsed(FlutterOptions.kExtraGenSnapshotOptions)) {
|
||||
return false;
|
||||
}
|
||||
switch (targetPlatform) {
|
||||
case TargetPlatform.android_arm:
|
||||
case TargetPlatform.android_arm64:
|
||||
case TargetPlatform.android_x86:
|
||||
case TargetPlatform.darwin_x64:
|
||||
return true;
|
||||
case TargetPlatform.android_x64:
|
||||
case TargetPlatform.ios:
|
||||
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,
|
||||
}) async {
|
||||
Status status;
|
||||
if (!argResults['quiet']) {
|
||||
final String typeName = artifacts.getEngineType(targetPlatform, buildMode);
|
||||
status = logger.startProgress(
|
||||
'Building AOT snapshot in ${getFriendlyModeName(getBuildMode())} mode ($typeName)...',
|
||||
timeout: timeoutConfiguration.slowOperation,
|
||||
);
|
||||
}
|
||||
final FlutterProject flutterProject = FlutterProject.current();
|
||||
// Currently this only supports android, per the check above.
|
||||
final Target target = buildMode == BuildMode.profile
|
||||
? const ProfileCopyFlutterAotBundle()
|
||||
: const ReleaseCopyFlutterAotBundle();
|
||||
|
||||
final BuildResult result = await buildSystem.build(target, Environment(
|
||||
projectDir: flutterProject.directory,
|
||||
outputDir: fs.directory(outputDir),
|
||||
buildDir: flutterProject.directory
|
||||
.childDirectory('.dart_tool')
|
||||
.childDirectory('flutter_build'),
|
||||
defines: <String, String>{
|
||||
kBuildMode: getNameForBuildMode(buildMode),
|
||||
kTargetPlatform: getNameForTargetPlatform(targetPlatform),
|
||||
kTargetFile: targetFile,
|
||||
}
|
||||
));
|
||||
status?.stop();
|
||||
if (!result.success) {
|
||||
for (ExceptionMeasurement measurement in result.exceptions.values) {
|
||||
printError(measurement.exception.toString());
|
||||
printError(measurement.stackTrace.toString());
|
||||
}
|
||||
throwToolExit('Failed to build aot.');
|
||||
}
|
||||
final String builtMessage = 'Built to $outputDir${fs.path.separator}.';
|
||||
if (argResults['quiet']) {
|
||||
printTrace(builtMessage);
|
||||
} else {
|
||||
printStatus(builtMessage);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> validateBitcode(BuildMode buildMode, TargetPlatform targetPlatform) async {
|
||||
final Artifacts artifacts = Artifacts.instance;
|
||||
final String flutterFrameworkPath = artifacts.getArtifactPath(
|
||||
Artifact.flutterFramework,
|
||||
mode: buildMode,
|
||||
platform: targetPlatform,
|
||||
);
|
||||
if (!fs.isDirectorySync(flutterFrameworkPath)) {
|
||||
throwToolExit('Flutter.framework not found at $flutterFrameworkPath');
|
||||
}
|
||||
final Xcode xcode = context.get<Xcode>();
|
||||
|
||||
final RunResult clangResult = await xcode.clang(<String>['--version']);
|
||||
final String clangVersion = clangResult.stdout.split('\n').first;
|
||||
final String engineClangVersion = PlistParser.instance.getValueFromFile(
|
||||
fs.path.join(flutterFrameworkPath, 'Info.plist'),
|
||||
'ClangVersion',
|
||||
);
|
||||
final Version engineClangSemVer = _parseVersionFromClang(engineClangVersion);
|
||||
final Version clangSemVer = _parseVersionFromClang(clangVersion);
|
||||
if (engineClangSemVer > clangSemVer) {
|
||||
throwToolExit(
|
||||
'The Flutter.framework at $flutterFrameworkPath was built '
|
||||
'with "${engineClangVersion ?? 'unknown'}", but the current version '
|
||||
'of clang is "$clangVersion". This will result in failures when trying to'
|
||||
'archive an IPA. To resolve this issue, update your version of Xcode to '
|
||||
'at least $engineClangSemVer.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Version _parseVersionFromClang(String clangVersion) {
|
||||
final RegExp pattern = RegExp(r'Apple (LLVM|clang) version (\d+\.\d+\.\d+) ');
|
||||
void _invalid() {
|
||||
throwToolExit('Unable to parse Clang version from "$clangVersion". '
|
||||
'Expected a string like "Apple (LLVM|clang) #.#.# (clang-####.#.##.#)".');
|
||||
}
|
||||
|
||||
if (clangVersion == null || clangVersion.isEmpty) {
|
||||
_invalid();
|
||||
}
|
||||
final RegExpMatch match = pattern.firstMatch(clangVersion);
|
||||
if (match == null || match.groupCount != 2) {
|
||||
_invalid();
|
||||
}
|
||||
final Version version = Version.parse(match.group(2));
|
||||
if (version == null) {
|
||||
_invalid();
|
||||
}
|
||||
return version;
|
||||
}
|
||||
|
@ -6,6 +6,7 @@ import 'dart:async';
|
||||
|
||||
import '../application_package.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../globals.dart';
|
||||
@ -13,6 +14,9 @@ import '../ios/mac.dart';
|
||||
import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
|
||||
import 'build.dart';
|
||||
|
||||
/// Builds an .app for an iOS app to be used for local testing on an iOS device
|
||||
/// or simulator. Can only be run on a macOS host. For producing deployment
|
||||
/// .ipas, see https://flutter.dev/docs/deployment/ios.
|
||||
class BuildIOSCommand extends BuildSubCommand {
|
||||
BuildIOSCommand() {
|
||||
usesTargetOption();
|
||||
@ -59,7 +63,7 @@ class BuildIOSCommand extends BuildSubCommand {
|
||||
final bool forSimulator = argResults['simulator'];
|
||||
defaultBuildMode = forSimulator ? BuildMode.debug : BuildMode.release;
|
||||
|
||||
if (getCurrentHostPlatform() != HostPlatform.darwin_x64) {
|
||||
if (!platform.isMacOS) {
|
||||
throwToolExit('Building for iOS is only supported on the Mac.');
|
||||
}
|
||||
|
||||
|
400
packages/flutter_tools/lib/src/commands/build_ios_framework.dart
Normal file
400
packages/flutter_tools/lib/src/commands/build_ios_framework.dart
Normal file
@ -0,0 +1,400 @@
|
||||
// Copyright 2019 The Chromium 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 'dart:async';
|
||||
|
||||
import 'package:file/file.dart';
|
||||
|
||||
import '../aot.dart';
|
||||
import '../application_package.dart';
|
||||
import '../artifacts.dart';
|
||||
import '../base/common.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/logger.dart';
|
||||
import '../base/platform.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/utils.dart';
|
||||
import '../build_info.dart';
|
||||
import '../build_system/targets/ios.dart';
|
||||
import '../bundle.dart';
|
||||
import '../globals.dart';
|
||||
import '../macos/cocoapod_utils.dart';
|
||||
import '../macos/xcode.dart';
|
||||
import '../plugins.dart';
|
||||
import '../project.dart';
|
||||
import '../runner/flutter_command.dart' show DevelopmentArtifact, FlutterCommandResult;
|
||||
import 'build.dart';
|
||||
|
||||
/// Produces a .framework for integration into a host iOS app. The .framework
|
||||
/// contains the Flutter engine and framework code as well as plugins. It can
|
||||
/// be integrated into plain Xcode projects without using or other package
|
||||
/// managers.
|
||||
class BuildIOSFrameworkCommand extends BuildSubCommand {
|
||||
BuildIOSFrameworkCommand({this.aotBuilder, this.bundleBuilder}) {
|
||||
usesTargetOption();
|
||||
usesFlavorOption();
|
||||
usesPubOption();
|
||||
argParser
|
||||
..addFlag('debug',
|
||||
negatable: true,
|
||||
defaultsTo: true,
|
||||
help: 'Whether to produce a framework for the debug build configuration. '
|
||||
'By default, all build configurations are built.'
|
||||
)
|
||||
..addFlag('profile',
|
||||
negatable: true,
|
||||
defaultsTo: true,
|
||||
help: 'Whether to produce a framework for the profile build configuration. '
|
||||
'By default, all build configurations are built.'
|
||||
)
|
||||
..addFlag('release',
|
||||
negatable: true,
|
||||
defaultsTo: true,
|
||||
help: 'Whether to produce a framework for the release build configuration. '
|
||||
'By default, all build configurations are built.'
|
||||
)
|
||||
..addFlag('universal',
|
||||
help: 'Produce universal frameworks that include all valid architectures. '
|
||||
'This is true by default.',
|
||||
defaultsTo: true,
|
||||
negatable: true
|
||||
)
|
||||
..addFlag('xcframework',
|
||||
help: 'Produce xcframeworks that include all valid architectures (Xcode 11 or later).',
|
||||
)
|
||||
..addOption('output',
|
||||
abbr: 'o',
|
||||
valueHelp: 'path/to/directory/',
|
||||
help: 'Location to write the frameworks.',
|
||||
);
|
||||
}
|
||||
|
||||
AotBuilder aotBuilder;
|
||||
BundleBuilder bundleBuilder;
|
||||
|
||||
@override
|
||||
final String name = 'ios-framework';
|
||||
|
||||
@override
|
||||
final String description = 'Produces a .framework directory for a Flutter module '
|
||||
'and its plugins for integration into existing, plain Xcode projects.\n'
|
||||
'This can only be run on macOS hosts.';
|
||||
|
||||
@override
|
||||
Future<Set<DevelopmentArtifact>> get requiredArtifacts async => const <DevelopmentArtifact>{
|
||||
DevelopmentArtifact.universal,
|
||||
DevelopmentArtifact.iOS,
|
||||
};
|
||||
|
||||
FlutterProject _project;
|
||||
|
||||
List<BuildMode> get buildModes {
|
||||
final List<BuildMode> buildModes = <BuildMode>[];
|
||||
|
||||
if (argResults['debug']) {
|
||||
buildModes.add(BuildMode.debug);
|
||||
}
|
||||
if (argResults['profile']) {
|
||||
buildModes.add(BuildMode.profile);
|
||||
}
|
||||
if (argResults['release']) {
|
||||
buildModes.add(BuildMode.release);
|
||||
}
|
||||
|
||||
return buildModes;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> validateCommand() async {
|
||||
await super.validateCommand();
|
||||
_project = FlutterProject.current();
|
||||
if (!_project.isModule) {
|
||||
throwToolExit('Building frameworks for iOS is only supported from a module.');
|
||||
}
|
||||
|
||||
if (!platform.isMacOS) {
|
||||
throwToolExit('Building frameworks for iOS is only supported on the Mac.');
|
||||
}
|
||||
|
||||
if (!argResults['universal'] && !argResults['xcframework']) {
|
||||
throwToolExit('--universal or --xcframework is required.');
|
||||
}
|
||||
if (argResults['xcframework'] && xcode.majorVersion < 11) {
|
||||
throwToolExit('--xcframework requires Xcode 11.');
|
||||
}
|
||||
if (buildModes.isEmpty) {
|
||||
throwToolExit('At least one of "--debug" or "--profile", or "--release" is required.');
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<FlutterCommandResult> runCommand() async {
|
||||
final String outputArgument = argResults['output']
|
||||
?? fs.path.join(fs.currentDirectory.path, 'build', 'ios', 'framework');
|
||||
|
||||
if (outputArgument.isEmpty) {
|
||||
throwToolExit('--output is required.');
|
||||
}
|
||||
|
||||
final BuildableIOSApp iosProject = await applicationPackages.getPackageForPlatform(TargetPlatform.ios);
|
||||
|
||||
if (iosProject == null) {
|
||||
throwToolExit("Module's iOS folder missing");
|
||||
}
|
||||
|
||||
final Directory outputDirectory = fs.directory(fs.path.normalize(outputArgument));
|
||||
|
||||
if (outputDirectory.existsSync()) {
|
||||
outputDirectory.deleteSync(recursive: true);
|
||||
}
|
||||
|
||||
aotBuilder ??= AotBuilder();
|
||||
bundleBuilder ??= BundleBuilder();
|
||||
|
||||
for (BuildMode mode in buildModes) {
|
||||
printStatus('Building framework for $iosProject in ${getNameForBuildMode(mode)} mode...');
|
||||
final String xcodeBuildConfiguration = toTitleCase(getNameForBuildMode(mode));
|
||||
final Directory modeDirectory = outputDirectory.childDirectory(xcodeBuildConfiguration);
|
||||
final Directory iPhoneBuildOutput = modeDirectory.childDirectory('iphoneos');
|
||||
final Directory simulatorBuildOutput = modeDirectory.childDirectory('iphonesimulator');
|
||||
|
||||
// Copy Flutter.framework.
|
||||
await _produceFlutterFramework(outputDirectory, mode, iPhoneBuildOutput, simulatorBuildOutput, modeDirectory);
|
||||
|
||||
// Build aot, create module.framework and copy.
|
||||
await _produceAppFramework(mode, iPhoneBuildOutput, modeDirectory);
|
||||
|
||||
// Build and copy plugins.
|
||||
await processPodsIfNeeded(_project.ios, getIosBuildDirectory(), mode);
|
||||
if (hasPlugins(_project)) {
|
||||
await _producePlugins(xcodeBuildConfiguration, iPhoneBuildOutput, simulatorBuildOutput, modeDirectory, outputDirectory);
|
||||
}
|
||||
|
||||
final Status status = logger.startProgress(' └─Moving to ${fs.path.relative(modeDirectory.path)}', timeout: timeoutConfiguration.slowOperation);
|
||||
// Delete the intermediaries since they would have been copied into our
|
||||
// output frameworks.
|
||||
if (iPhoneBuildOutput.existsSync()) {
|
||||
iPhoneBuildOutput.deleteSync(recursive: true);
|
||||
}
|
||||
if (simulatorBuildOutput.existsSync()) {
|
||||
simulatorBuildOutput.deleteSync(recursive: true);
|
||||
}
|
||||
status.stop();
|
||||
}
|
||||
|
||||
printStatus('Frameworks written to ${outputDirectory.path}.');
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
Future<void> _produceFlutterFramework(Directory outputDirectory, BuildMode mode, Directory iPhoneBuildOutput, Directory simulatorBuildOutput, Directory modeDirectory) async {
|
||||
final Status status = logger.startProgress(' ├─Populating Flutter.framework...', timeout: timeoutConfiguration.fastOperation);
|
||||
final String engineCacheFlutterFrameworkDirectory = artifacts.getArtifactPath(Artifact.flutterFramework, platform: TargetPlatform.ios, mode: mode);
|
||||
|
||||
// Copy universal engine cache framework to mode directory.
|
||||
final String flutterFrameworkFileName = fs.path.basename(engineCacheFlutterFrameworkDirectory);
|
||||
final Directory fatFlutterFrameworkCopy = modeDirectory.childDirectory(flutterFrameworkFileName);
|
||||
copyDirectorySync(fs.directory(engineCacheFlutterFrameworkDirectory), fatFlutterFrameworkCopy);
|
||||
|
||||
if (argResults['xcframework']) {
|
||||
// Copy universal framework to variant directory.
|
||||
final Directory armFlutterFrameworkDirectory = iPhoneBuildOutput.childDirectory(flutterFrameworkFileName);
|
||||
final File armFlutterFrameworkBinary = armFlutterFrameworkDirectory.childFile('Flutter');
|
||||
final File fatFlutterFrameworkBinary = fatFlutterFrameworkCopy.childFile('Flutter');
|
||||
copyDirectorySync(fatFlutterFrameworkCopy, armFlutterFrameworkDirectory);
|
||||
|
||||
// Create iOS framework.
|
||||
List<String> lipoCommand = <String>['xcrun', 'lipo', fatFlutterFrameworkBinary.path, '-remove', 'x86_64', '-output', armFlutterFrameworkBinary.path];
|
||||
|
||||
await processUtils.run(
|
||||
lipoCommand,
|
||||
workingDirectory: outputDirectory.path,
|
||||
allowReentrantFlutter: false,
|
||||
);
|
||||
|
||||
// Create simulator framework.
|
||||
final Directory simulatorFlutterFrameworkDirectory = simulatorBuildOutput.childDirectory(flutterFrameworkFileName);
|
||||
final File simulatorFlutterFrameworkBinary = simulatorFlutterFrameworkDirectory.childFile('Flutter');
|
||||
copyDirectorySync(fatFlutterFrameworkCopy, simulatorFlutterFrameworkDirectory);
|
||||
|
||||
lipoCommand = <String>['xcrun', 'lipo', fatFlutterFrameworkBinary.path, '-thin', 'x86_64', '-output', simulatorFlutterFrameworkBinary.path];
|
||||
|
||||
await processUtils.run(
|
||||
lipoCommand,
|
||||
workingDirectory: outputDirectory.path,
|
||||
allowReentrantFlutter: false,
|
||||
);
|
||||
|
||||
// Create XCFramework from iOS and simulator frameworks.
|
||||
final List<String> xcframeworkCommand = <String>[
|
||||
'xcrun',
|
||||
'xcodebuild',
|
||||
'-create-xcframework',
|
||||
'-framework', armFlutterFrameworkDirectory.path,
|
||||
'-framework', simulatorFlutterFrameworkDirectory.path,
|
||||
'-output', modeDirectory
|
||||
.childFile('Flutter.xcframework')
|
||||
.path
|
||||
];
|
||||
|
||||
await processUtils.run(
|
||||
xcframeworkCommand,
|
||||
workingDirectory: outputDirectory.path,
|
||||
allowReentrantFlutter: false,
|
||||
);
|
||||
}
|
||||
|
||||
if (!argResults['universal']) {
|
||||
fatFlutterFrameworkCopy.deleteSync(recursive: true);
|
||||
}
|
||||
status.stop();
|
||||
}
|
||||
|
||||
Future<void> _produceAppFramework(BuildMode mode, Directory iPhoneBuildOutput, Directory modeDirectory) async {
|
||||
const String appFrameworkName = 'App.framework';
|
||||
final Directory destinationAppFrameworkDirectory = modeDirectory.childDirectory(appFrameworkName);
|
||||
|
||||
if (mode == BuildMode.debug) {
|
||||
final Status status = logger.startProgress(' ├─Add placeholder App.framework for debug...', timeout: timeoutConfiguration.fastOperation);
|
||||
await createStubAppFramework(destinationAppFrameworkDirectory);
|
||||
status.stop();
|
||||
} else {
|
||||
await _produceAotAppFrameworkIfNeeded(mode, iPhoneBuildOutput, destinationAppFrameworkDirectory);
|
||||
}
|
||||
|
||||
final File sourceInfoPlist = _project.ios.hostAppRoot.childDirectory('Flutter').childFile('AppFrameworkInfo.plist');
|
||||
final File destinationInfoPlist = destinationAppFrameworkDirectory.childFile('Info.plist')..createSync(recursive: true);
|
||||
|
||||
destinationInfoPlist.writeAsBytesSync(sourceInfoPlist.readAsBytesSync());
|
||||
|
||||
final Status status = logger.startProgress(' ├─Assembling Flutter resources for App.framework...', timeout: timeoutConfiguration.slowOperation);
|
||||
await bundleBuilder.build(
|
||||
platform: TargetPlatform.ios,
|
||||
buildMode: mode,
|
||||
// Relative paths show noise in the compiler https://github.com/dart-lang/sdk/issues/37978.
|
||||
mainPath: fs.path.absolute(targetFile),
|
||||
assetDirPath: destinationAppFrameworkDirectory.childDirectory('flutter_assets').path,
|
||||
precompiledSnapshot: mode != BuildMode.debug,
|
||||
);
|
||||
status.stop();
|
||||
}
|
||||
|
||||
Future<void> _produceAotAppFrameworkIfNeeded(BuildMode mode, Directory iPhoneBuildOutput, Directory destinationAppFrameworkDirectory) async {
|
||||
if (mode == BuildMode.debug) {
|
||||
return;
|
||||
}
|
||||
final Status status = logger.startProgress(' ├─Building Dart AOT for App.framework...', timeout: timeoutConfiguration.slowOperation);
|
||||
await aotBuilder.build(
|
||||
platform: TargetPlatform.ios,
|
||||
outputPath: iPhoneBuildOutput.path,
|
||||
buildMode: mode,
|
||||
// Relative paths show noise in the compiler https://github.com/dart-lang/sdk/issues/37978.
|
||||
mainDartFile: fs.path.absolute(targetFile),
|
||||
quiet: true,
|
||||
reportTimings: false,
|
||||
iosBuildArchs: <DarwinArch>[DarwinArch.armv7, DarwinArch.arm64],
|
||||
);
|
||||
|
||||
const String appFrameworkName = 'App.framework';
|
||||
copyDirectorySync(iPhoneBuildOutput.childDirectory(appFrameworkName), destinationAppFrameworkDirectory);
|
||||
status.stop();
|
||||
}
|
||||
|
||||
Future<void> _producePlugins(
|
||||
String xcodeBuildConfiguration,
|
||||
Directory iPhoneBuildOutput,
|
||||
Directory simulatorBuildOutput,
|
||||
Directory modeDirectory,
|
||||
Directory outputDirectory,
|
||||
) async {
|
||||
final Status status = logger.startProgress(' ├─Building plugins...', timeout: timeoutConfiguration.slowOperation);
|
||||
List<String> pluginsBuildCommand = <String>[
|
||||
'xcrun',
|
||||
'xcodebuild',
|
||||
'-alltargets',
|
||||
'-sdk',
|
||||
'iphoneos',
|
||||
'-configuration',
|
||||
xcodeBuildConfiguration,
|
||||
'SYMROOT=${iPhoneBuildOutput.path}',
|
||||
'ONLY_ACTIVE_ARCH=NO' // No device targeted, so build all valid architectures.
|
||||
];
|
||||
|
||||
await processUtils.run(
|
||||
pluginsBuildCommand,
|
||||
workingDirectory: _project.ios.hostAppRoot.childDirectory('Pods').path,
|
||||
allowReentrantFlutter: false,
|
||||
);
|
||||
|
||||
pluginsBuildCommand = <String>[
|
||||
'xcrun',
|
||||
'xcodebuild',
|
||||
'-alltargets',
|
||||
'-sdk',
|
||||
'iphonesimulator',
|
||||
'-configuration',
|
||||
xcodeBuildConfiguration,
|
||||
'SYMROOT=${simulatorBuildOutput.path}',
|
||||
'ARCHS=x86_64',
|
||||
'ONLY_ACTIVE_ARCH=NO' // No device targeted, so build all valid architectures.
|
||||
];
|
||||
|
||||
await processUtils.run(
|
||||
pluginsBuildCommand,
|
||||
workingDirectory: _project.ios.hostAppRoot.childDirectory('Pods').path,
|
||||
allowReentrantFlutter: false,
|
||||
);
|
||||
|
||||
final Directory iPhoneBuildConfiguration = iPhoneBuildOutput.childDirectory('$xcodeBuildConfiguration-iphoneos');
|
||||
final Directory simulatorBuildConfiguration = simulatorBuildOutput.childDirectory('$xcodeBuildConfiguration-iphonesimulator');
|
||||
|
||||
for (Directory builtProduct in iPhoneBuildConfiguration.listSync(followLinks: false).whereType<Directory>()) {
|
||||
for (FileSystemEntity podProduct in builtProduct.listSync(followLinks: false)) {
|
||||
final String podFrameworkName = podProduct.basename;
|
||||
if (fs.path.extension(podFrameworkName) == '.framework') {
|
||||
final String binaryName = fs.path.basenameWithoutExtension(podFrameworkName);
|
||||
if (argResults['universal']) {
|
||||
copyDirectorySync(podProduct, modeDirectory.childDirectory(podFrameworkName));
|
||||
final List<String> lipoCommand = <String>[
|
||||
'xcrun',
|
||||
'lipo',
|
||||
'-create',
|
||||
fs.path.join(podProduct.path, binaryName),
|
||||
simulatorBuildConfiguration.childDirectory(binaryName).childDirectory(podFrameworkName).childFile(binaryName).path,
|
||||
'-output',
|
||||
modeDirectory.childDirectory(podFrameworkName).childFile(binaryName).path
|
||||
];
|
||||
|
||||
await processUtils.run(
|
||||
lipoCommand,
|
||||
workingDirectory: outputDirectory.path,
|
||||
allowReentrantFlutter: false,
|
||||
);
|
||||
}
|
||||
|
||||
if (argResults['xcframework']) {
|
||||
final List<String> xcframeworkCommand = <String>[
|
||||
'xcrun',
|
||||
'xcodebuild',
|
||||
'-create-xcframework',
|
||||
'-framework',
|
||||
podProduct.path,
|
||||
'-framework',
|
||||
simulatorBuildConfiguration.childDirectory(binaryName).childDirectory(podFrameworkName).path,
|
||||
'-output',
|
||||
modeDirectory.childFile('$binaryName.xcframework').path
|
||||
];
|
||||
|
||||
await processUtils.run(
|
||||
xcframeworkCommand,
|
||||
workingDirectory: outputDirectory.path,
|
||||
allowReentrantFlutter: false,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
status.stop();
|
||||
}
|
||||
}
|
67
packages/flutter_tools/lib/src/ios/bitcode.dart
Normal file
67
packages/flutter_tools/lib/src/ios/bitcode.dart
Normal file
@ -0,0 +1,67 @@
|
||||
// Copyright 2019 The Chromium 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/common.dart';
|
||||
import '../base/context.dart';
|
||||
import '../base/file_system.dart';
|
||||
import '../base/process.dart';
|
||||
import '../base/version.dart';
|
||||
import '../build_info.dart';
|
||||
import '../ios/plist_parser.dart';
|
||||
import '../macos/xcode.dart';
|
||||
|
||||
const bool kBitcodeEnabledDefault = false;
|
||||
|
||||
Future<void> validateBitcode(BuildMode buildMode, TargetPlatform targetPlatform) async {
|
||||
final Artifacts artifacts = Artifacts.instance;
|
||||
final String flutterFrameworkPath = artifacts.getArtifactPath(
|
||||
Artifact.flutterFramework,
|
||||
mode: buildMode,
|
||||
platform: targetPlatform,
|
||||
);
|
||||
if (!fs.isDirectorySync(flutterFrameworkPath)) {
|
||||
throwToolExit('Flutter.framework not found at $flutterFrameworkPath');
|
||||
}
|
||||
final Xcode xcode = context.get<Xcode>();
|
||||
|
||||
final RunResult clangResult = await xcode.clang(<String>['--version']);
|
||||
final String clangVersion = clangResult.stdout.split('\n').first;
|
||||
final String engineClangVersion = PlistParser.instance.getValueFromFile(
|
||||
fs.path.join(flutterFrameworkPath, 'Info.plist'),
|
||||
'ClangVersion',
|
||||
);
|
||||
final Version engineClangSemVer = _parseVersionFromClang(engineClangVersion);
|
||||
final Version clangSemVer = _parseVersionFromClang(clangVersion);
|
||||
if (engineClangSemVer > clangSemVer) {
|
||||
throwToolExit(
|
||||
'The Flutter.framework at $flutterFrameworkPath was built '
|
||||
'with "${engineClangVersion ?? 'unknown'}", but the current version '
|
||||
'of clang is "$clangVersion". This will result in failures when trying to'
|
||||
'archive an IPA. To resolve this issue, update your version of Xcode to '
|
||||
'at least $engineClangSemVer.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Version _parseVersionFromClang(String clangVersion) {
|
||||
final RegExp pattern = RegExp(r'Apple (LLVM|clang) version (\d+\.\d+\.\d+) ');
|
||||
void _invalid() {
|
||||
throwToolExit('Unable to parse Clang version from "$clangVersion". '
|
||||
'Expected a string like "Apple (LLVM|clang) #.#.# (clang-####.#.##.#)".');
|
||||
}
|
||||
|
||||
if (clangVersion == null || clangVersion.isEmpty) {
|
||||
_invalid();
|
||||
}
|
||||
final RegExpMatch match = pattern.firstMatch(clangVersion);
|
||||
if (match == null || match.groupCount != 2) {
|
||||
_invalid();
|
||||
}
|
||||
final Version version = Version.parse(match.group(2));
|
||||
if (version == null) {
|
||||
_invalid();
|
||||
}
|
||||
return version;
|
||||
}
|
@ -7,8 +7,8 @@ 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/build_info.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/bitcode.dart';
|
||||
import 'package:flutter_tools/src/ios/plist_parser.dart';
|
||||
import 'package:flutter_tools/src/macos/xcode.dart';
|
||||
import 'package:mockito/mockito.dart';
|
||||
|
Loading…
x
Reference in New Issue
Block a user