Unverified Commit d2e87a5d authored by xster's avatar xster Committed by GitHub

Build ios framework (#44065)

parent 028ed712
......@@ -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));
......
// 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(
......
// 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'];
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);
}
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.',
aotBuilder ??= AotBuilder();
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;
}
}
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.');
}
......
// 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();
}
}
// 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';
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment