Skip to content
Browse files

Use Xcode legacy build system for iOS builds

Xcode 10 introduces a new build system which includes stricter checks on
duplicate build outputs.

When plugins are in use, there are two competing build actions that copy
Flutter.framework into the build application Frameworks directory:

  1. The Embed Frameworks build phase for the Runner project
  2. The [CP] Embed Pods Frameworks build phase that pod install creates
     in the project.

Item (1) is there to ensure the framework is copied into the built app
in the case where there are no plugins (and therefore no CocoaPods
integration in the Xcode project). Item (2) is there because Flutter's
podspec declares Flutter.framework as a vended_framework, and CocoaPods
automatically adds a copy step for each such vended_framework in the
transitive closure of CocoaPods dependencies.

As an immediate fix, we opt back into the build system used by Xcode 9
and earlier. Longer term, we need to update our templates and
flutter_tools to correctly handle this situation.

See: flutter#20685
  • Loading branch information...
cbracken committed Sep 15, 2018
1 parent 1ad538e commit dad496378003a8b60eee40ca356639cd5e291ddb
Showing with 73 additions and 0 deletions.
  1. +64 −0 packages/flutter_tools/lib/src/ios/mac.dart
  2. +9 −0 packages/flutter_tools/lib/src/project.dart
@@ -35,6 +35,42 @@ IMobileDevice get iMobileDevice => context[IMobileDevice];

Xcode get xcode => context[Xcode];

/// A property list is a key-value representation commonly used for
/// configuration on macOS/iOS systems.
class PropertyList {
const PropertyList(this.plistPath);

static const String _toolPath = '/usr/libexec/PlistBuddy';
final String plistPath;

/// Prints the specified key, or returns null if not present.
Future<String> read(String key) async {
final ProcessResult result = await _runCommand('Print $key');
if (result.exitCode == 0)
return result.stdout.trim();
return null;

/// Adds [key]. Has no effect if the key already exists.
Future<void> addString(String key, String value) async {
await _runCommand('Add $key string $value');

/// Updates [key] with the new [value]. Has no effect if the key does not exist.
Future<void> update(String key, String value) async {
await _runCommand('Set $key $value');

/// Deletes [key].
Future<void> delete(String key) async {
await _runCommand('Delete $key');

Future<ProcessResult> _runCommand(String command) async {
return await<String>[_toolPath, '-c', command, plistPath]);

class IMobileDevice {
const IMobileDevice();

@@ -181,6 +217,27 @@ class Xcode {

Future<void> setXcodeWorkspaceBuildSystem({
@required File workspaceSettings,
@required bool modern,
}) async {
if (!workspaceSettings.existsSync())
workspaceSettings.createSync(recursive: true);

const String kBuildSystemType = 'BuildSystemType';
final PropertyList plist = PropertyList(workspaceSettings.path);
if (modern) {
await plist.delete(kBuildSystemType);
} else {
// Use legacy build system.
if (await == null) {
await plist.addString(kBuildSystemType, 'Original');
} else {
await plist.update(kBuildSystemType, 'Original');

Future<XcodeBuildResult> buildXcodeProject({
BuildableIOSApp app,
BuildInfo buildInfo,
@@ -195,6 +252,13 @@ Future<XcodeBuildResult> buildXcodeProject({
if (!_checkXcodeVersion())
return XcodeBuildResult(success: false);

// TODO(cbracken) remove when is fixed.
printTrace('Using Xcode legacy build system.');
await setXcodeWorkspaceBuildSystem(
workspaceSettings: app.project.xcodeWorkspaceSharedSettings,
modern: false,

final XcodeProjectInfo projectInfo = xcodeProjectInterpreter.getInfo(;
if (!projectInfo.targets.contains('Runner')) {
printError('The Xcode project does not define target "Runner" which is needed by Flutter tooling.');
@@ -186,6 +186,15 @@ class IosProject {
/// The '.pbxproj' file of the host app.
File get xcodeProjectInfoFile => xcodeProject.childFile('project.pbxproj');

/// Xcode workspace directory of the host app.
Directory get xcodeWorkspace => directory.childDirectory('$_hostAppBundleName.xcworkspace');

/// Xcode workspace shared data directory for the host app.
Directory get xcodeWorkspaceSharedData => xcodeWorkspace.childDirectory('xcshareddata');

/// Xcode workspace shared workspace settings file for the host app.
File get xcodeWorkspaceSharedSettings => xcodeWorkspaceSharedData.childFile('WorkspaceSettings.xcsettings');

/// The product bundle identifier of the host app, or null if not set or if
/// iOS tooling needed to read it is not installed.
String get productBundleIdentifier {

0 comments on commit dad4963

Please sign in to comment.
You can’t perform that action at this time.