Support Xcode variable substitution in Info.plist

As of Xcode 7, Apple recommends setting CFBundleIdentifier to
$(PRODUCT_BUNDLE_IDENTIFIER).
diff --git a/packages/flutter_tools/lib/src/application_package.dart b/packages/flutter_tools/lib/src/application_package.dart
index 29e374b..cf8ae2b 100644
--- a/packages/flutter_tools/lib/src/application_package.dart
+++ b/packages/flutter_tools/lib/src/application_package.dart
@@ -12,6 +12,7 @@
 import 'build_info.dart';
 import 'globals.dart';
 import 'ios/plist_utils.dart';
+import 'ios/xcodeproj.dart';
 
 abstract class ApplicationPackage {
   /// Package ID from the Android Manifest or equivalent.
@@ -137,6 +138,8 @@
     String value = getValueFromFile(plistPath, kCFBundleIdentifierKey);
     if (value == null)
       return null;
+    String projectPath = path.join('ios', 'Runner.xcodeproj');
+    value = substituteXcodeVariables(value, projectPath, 'Runner');
 
     return new IOSApp(
       appDirectory: path.join('ios'),
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index 78df9a2..d180827 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -15,7 +15,7 @@
 import '../flx.dart' as flx;
 import '../globals.dart';
 import '../services.dart';
-import 'setup_xcodeproj.dart';
+import 'xcodeproj.dart';
 
 String get homeDirectory => path.absolute(Platform.environment['HOME']);
 
diff --git a/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
similarity index 62%
rename from packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart
rename to packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 507b70c..6d1bc49 100644
--- a/packages/flutter_tools/lib/src/ios/setup_xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -6,10 +6,14 @@
 
 import 'package:path/path.dart' as path;
 
+import '../base/process.dart';
 import '../build_info.dart';
 import '../cache.dart';
 import '../globals.dart';
 
+final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(\S+)');
+final RegExp _varExpr = new RegExp(r'\$\((.*)\)');
+
 void updateXcodeGeneratedProperties(String projectPath, BuildMode mode, String target) {
   StringBuffer localsBuffer = new StringBuffer();
 
@@ -43,3 +47,28 @@
   localsFile.createSync(recursive: true);
   localsFile.writeAsStringSync(localsBuffer.toString());
 }
+
+Map<String, String> getXcodeBuildSettings(String xcodeProjPath, String target) {
+  String absProjPath = path.absolute(xcodeProjPath);
+  String out = runCheckedSync(<String>[
+    '/usr/bin/xcodebuild', '-project', absProjPath, '-target', target, '-showBuildSettings'
+  ]);
+  Map<String, String> settings = <String, String>{};
+  for (String line in out.split('\n').where(_settingExpr.hasMatch)) {
+    Match match = _settingExpr.firstMatch(line);
+    settings[match[1]] = match[2];
+  }
+  return settings;
+}
+
+
+/// Substitutes variables in [str] with their values from the specified Xcode
+/// project and target.
+String substituteXcodeVariables(String str, String xcodeProjPath, String target) {
+  Iterable<Match> matches = _varExpr.allMatches(str);
+  if (matches.isEmpty)
+    return str;
+
+  Map<String, String> settings = getXcodeBuildSettings(xcodeProjPath, target);
+  return str.replaceAllMapped(_varExpr, (Match m) => settings[m[1]] ?? m[0]);
+}