add version to pubspec.yaml (#16857)

Uses the `version` property from the `pubspec.yaml` file to set the corresponding fields in the `local.properties` file respectively in the `Generated.xcconfig` file.

The `--build-name` and `--build-number` options have changed. Now they trump the `version` property from the `pubspec.yaml` file.

If the `version` property is not set and the  `--build-name` and `--build-number` options are not provided, the build command will not change the `local.properties` / `Generated.xcconfig` file.
diff --git a/packages/flutter_tools/lib/src/android/gradle.dart b/packages/flutter_tools/lib/src/android/gradle.dart
index 9825ffd..b686e31 100644
--- a/packages/flutter_tools/lib/src/android/gradle.dart
+++ b/packages/flutter_tools/lib/src/android/gradle.dart
@@ -3,9 +3,6 @@
 // found in the LICENSE file.
 
 import 'dart:async';
-import 'dart:convert';
-
-import 'package:meta/meta.dart';
 
 import '../android/android_sdk.dart';
 import '../artifacts.dart';
@@ -17,7 +14,9 @@
 import '../base/process.dart';
 import '../base/utils.dart';
 import '../build_info.dart';
+import '../bundle.dart' as bundle;
 import '../cache.dart';
+import '../flutter_manifest.dart';
 import '../globals.dart';
 import 'android_sdk.dart';
 import 'android_studio.dart';
@@ -95,7 +94,7 @@
 // of calculating the app properties using Gradle. This may take minutes.
 Future<GradleProject> _readGradleProject() async {
   final String gradle = await _ensureGradle();
-  updateLocalProperties();
+  await updateLocalProperties();
   try {
     final Status status = logger.startProgress('Resolving dependencies...', expectSlowOperation: true);
     final RunResult runResult = await runCheckedAsync(
@@ -198,10 +197,13 @@
 }
 
 /// Create android/local.properties if needed, and update Flutter settings.
-void updateLocalProperties({String projectPath, BuildInfo buildInfo}) {
+Future<void> updateLocalProperties({String projectPath, BuildInfo buildInfo}) async {
   final File localProperties = (projectPath == null)
       ? fs.file(fs.path.join('android', 'local.properties'))
       : fs.file(fs.path.join(projectPath, 'android', 'local.properties'));
+  final String flutterManifest = (projectPath == null)
+      ? fs.path.join(bundle.defaultManifestPath)
+      : fs.path.join(projectPath, bundle.defaultManifestPath);
   bool changed = false;
 
   SettingsFile settings;
@@ -225,85 +227,39 @@
     changed = true;
   }
 
+  FlutterManifest manifest;
+  try {
+    manifest = await FlutterManifest.createFromPath(flutterManifest);
+  } catch (error) {
+    throwToolExit('Failed to load pubspec.yaml: $error');
+  }
+
+  final String buildName = buildInfo?.buildName ?? manifest.buildName;
+  if (buildName != null) {
+    settings.values['flutter.versionName'] = buildName;
+    changed = true;
+  }
+
+  final int buildNumber = buildInfo?.buildNumber ?? manifest.buildNumber;
+  if (buildNumber != null) {
+    settings.values['flutter.versionCode'] = '$buildNumber';
+    changed = true;
+  }
+
   if (changed)
     settings.writeContents(localProperties);
 }
 
-Future<Null> findAndReplaceVersionProperties({@required BuildInfo buildInfo}) {
-  assert(buildInfo != null, 'buildInfo can\'t be null');
-  final Completer<Null> completer = new Completer<Null>();
-
-  // early return, if nothing has to be changed
-  if (buildInfo.buildNumber == null && buildInfo.buildName == null) {
-    completer.complete();
-    return completer.future;
-  }
-
-  final File appGradle = fs.file(fs.path.join('android', 'app', 'build.gradle'));
-  final File appGradleTmp = fs.file(fs.path.join('android', 'app', 'build.gradle.tmp'));
-  appGradleTmp.createSync();
-
-  if (appGradle.existsSync() && appGradleTmp.existsSync()) {
-    final Stream<List<int>> inputStream = appGradle.openRead();
-    final IOSink sink = appGradleTmp.openWrite();
-
-    inputStream.transform(utf8.decoder)
-        .transform(const LineSplitter())
-        .map((String line) {
-
-          // find and replace build number
-          if (buildInfo.buildNumber != null) {
-            if (line.contains(new RegExp(r'^[ |\t]*(versionCode)[ =\t]*\d*'))) {
-              return line.splitMapJoin(new RegExp(r'(versionCode)[ =\t]*\d*'), onMatch: (Match m) {
-                return 'versionCode ${buildInfo.buildNumber}';
-              });
-            }
-          }
-
-          // find and replace build name
-          if (buildInfo.buildName != null) {
-            if (line.contains(new RegExp(r'^[ |\t]*(versionName)[ =\t]*\"[0-9.]*"'))) {
-              return line.splitMapJoin(new RegExp(r'(versionName)[ =\t]*\"[0-9.]*"'), onMatch: (Match m) {
-                return 'versionName "${buildInfo.buildName}"';
-              });
-            }
-          }
-          return line;
-        })
-        .listen((String line) {
-            sink.writeln(line);
-          },
-          onDone: () {
-            sink.close();
-            try {
-              final File gradleOld = appGradle.renameSync(fs.path.join('android', 'app', 'build.gradle.old'));
-              appGradleTmp.renameSync(fs.path.join('android', 'app', 'build.gradle'));
-              gradleOld.deleteSync();
-              completer.complete();
-            } catch (error) {
-              printError('Failed to change version properties. $error');
-              completer.completeError('Failed to change version properties. $error');
-            }
-          },
-          onError: (dynamic error, StackTrace stack) {
-            printError('Failed to change version properties. ${error.toString()}');
-            sink.close();
-            appGradleTmp.deleteSync();
-            completer.completeError('Failed to change version properties. ${error.toString()}', stack);
-          },
-    );
-  }
-
-  return completer.future;
-}
-
 Future<Null> buildGradleProject(BuildInfo buildInfo, String target) async {
-  // Update the local.properties file with the build mode.
+  // Update the local.properties file with the build mode, version name and code.
   // FlutterPlugin v1 reads local.properties to determine build mode. Plugin v2
   // uses the standard Android way to determine what to build, but we still
   // update local.properties, in case we want to use it in the future.
-  updateLocalProperties(buildInfo: buildInfo);
-  await findAndReplaceVersionProperties(buildInfo: buildInfo);
+  // Version name and number are provided by the pubspec.yaml file
+  // and can be overwritten with flutter build command.
+  // The default Gradle script reads the version name and number
+  // from the local.properties file.
+  await updateLocalProperties(buildInfo: buildInfo);
 
   final String gradle = await _ensureGradle();
 
diff --git a/packages/flutter_tools/lib/src/commands/create.dart b/packages/flutter_tools/lib/src/commands/create.dart
index 4a7eb6a..e8c49ff 100644
--- a/packages/flutter_tools/lib/src/commands/create.dart
+++ b/packages/flutter_tools/lib/src/commands/create.dart
@@ -289,11 +289,11 @@
 
     if (argResults['pub']) {
       await pubGet(context: PubContext.create, directory: appPath, offline: argResults['offline']);
-      new FlutterProject(fs.directory(appPath)).ensureReadyForPlatformSpecificTooling();
+      await new FlutterProject(fs.directory(appPath)).ensureReadyForPlatformSpecificTooling();
     }
 
     if (android_sdk.androidSdk != null)
-      gradle.updateLocalProperties(projectPath: appPath);
+      await gradle.updateLocalProperties(projectPath: appPath);
 
     return generatedCount;
   }
diff --git a/packages/flutter_tools/lib/src/commands/packages.dart b/packages/flutter_tools/lib/src/commands/packages.dart
index 7d61ed2..471e0a6 100644
--- a/packages/flutter_tools/lib/src/commands/packages.dart
+++ b/packages/flutter_tools/lib/src/commands/packages.dart
@@ -82,13 +82,13 @@
 
     await _runPubGet(target);
     final FlutterProject rootProject = new FlutterProject(fs.directory(target));
-    rootProject.ensureReadyForPlatformSpecificTooling();
+    await rootProject.ensureReadyForPlatformSpecificTooling();
 
     // Get/upgrade packages in example app as well
     if (rootProject.hasExampleApp) {
       final FlutterProject exampleProject = rootProject.example;
       await _runPubGet(exampleProject.directory.path);
-      exampleProject.ensureReadyForPlatformSpecificTooling();
+      await exampleProject.ensureReadyForPlatformSpecificTooling();
     }
   }
 }
diff --git a/packages/flutter_tools/lib/src/flutter_manifest.dart b/packages/flutter_tools/lib/src/flutter_manifest.dart
index 0c26c47..c151839 100644
--- a/packages/flutter_tools/lib/src/flutter_manifest.dart
+++ b/packages/flutter_tools/lib/src/flutter_manifest.dart
@@ -13,6 +13,8 @@
 import 'cache.dart';
 import 'globals.dart';
 
+final RegExp _versionPattern = new RegExp(r'^(\d+)(\.(\d+)(\.(\d+))?)?(\+(\d+))?$');
+
 /// A wrapper around the `flutter` section in the `pubspec.yaml` file.
 class FlutterManifest {
   FlutterManifest._();
@@ -51,6 +53,36 @@
 
   String get appName => _descriptor['name'] ?? '';
 
+  /// The version String from the `pubspec.yaml` file.
+  /// Can be null if it isn't set or has a wrong format.
+  String get appVersion {
+    final String version = _descriptor['version']?.toString();
+    if (version != null && _versionPattern.hasMatch(version))
+      return version;
+    else
+      return null;
+  }
+
+  /// The build version name from the `pubspec.yaml` file.
+  /// Can be null if version isn't set or has a wrong format.
+  String get buildName {
+    if (appVersion != null && appVersion.contains('+'))
+      return appVersion.split('+')?.elementAt(0);
+    else
+      return appVersion;
+  }
+
+  /// The build version number from the `pubspec.yaml` file.
+  /// Can be null if version isn't set or has a wrong format.
+  int get buildNumber {
+    if (appVersion != null && appVersion.contains('+')) {
+      final String value = appVersion.split('+')?.elementAt(1);
+      return value == null ? null : int.tryParse(value);
+    } else {
+      return null;
+    }
+  }
+
   bool get usesMaterialDesign {
     return _flutterDescriptor['uses-material-design'] ?? false;
   }
diff --git a/packages/flutter_tools/lib/src/ios/mac.dart b/packages/flutter_tools/lib/src/ios/mac.dart
index fa2d3e5..c4c1453 100644
--- a/packages/flutter_tools/lib/src/ios/mac.dart
+++ b/packages/flutter_tools/lib/src/ios/mac.dart
@@ -242,7 +242,7 @@
   final Directory appDirectory = fs.directory(app.appDirectory);
   await _addServicesToBundle(appDirectory);
 
-  updateGeneratedXcodeProperties(
+  await updateGeneratedXcodeProperties(
     projectPath: fs.currentDirectory.path,
     buildInfo: buildInfo,
     targetOverride: targetOverride,
@@ -272,53 +272,6 @@
       await fingerprinter.writeFingerprint();
   }
 
-  // If buildNumber is not specified, keep the project untouched.
-  if (buildInfo.buildNumber != null) {
-    final Status buildNumberStatus =
-        logger.startProgress('Setting CFBundleVersion...', expectSlowOperation: true);
-    try {
-      final RunResult buildNumberResult = await runAsync(
-        <String>[
-          '/usr/bin/env',
-          'xcrun',
-          'agvtool',
-          'new-version',
-          '-all',
-          buildInfo.buildNumber.toString(),
-        ],
-        workingDirectory: app.appDirectory,
-      );
-      if (buildNumberResult.exitCode != 0) {
-        throwToolExit('Xcode failed to set new version\n${buildNumberResult.stderr}');
-      }
-    } finally {
-      buildNumberStatus.stop();
-    }
-  }
-
-  // If buildName is not specified, keep the project untouched.
-  if (buildInfo.buildName != null) {
-    final Status buildNameStatus =
-        logger.startProgress('Setting CFBundleShortVersionString...', expectSlowOperation: true);
-    try {
-      final RunResult buildNameResult = await runAsync(
-        <String>[
-          '/usr/bin/env',
-          'xcrun',
-          'agvtool',
-          'new-marketing-version',
-          buildInfo.buildName,
-        ],
-        workingDirectory: app.appDirectory,
-      );
-      if (buildNameResult.exitCode != 0) {
-        throwToolExit('Xcode failed to set new marketing version\n${buildNameResult.stderr}');
-      }
-    } finally {
-      buildNameStatus.stop();
-    }
-  }
-
   final List<String> buildCommands = <String>[
     '/usr/bin/env',
     'xcrun',
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 7d00695..38ba246 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -2,9 +2,12 @@
 // 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 '../artifacts.dart';
+import '../base/common.dart';
 import '../base/context.dart';
 import '../base/file_system.dart';
 import '../base/io.dart';
@@ -15,6 +18,7 @@
 import '../build_info.dart';
 import '../bundle.dart' as bundle;
 import '../cache.dart';
+import '../flutter_manifest.dart';
 import '../globals.dart';
 
 final RegExp _settingExpr = new RegExp(r'(\w+)\s*=\s*(.*)$');
@@ -30,11 +34,11 @@
 
 /// Writes default Xcode properties files in the Flutter project at [projectPath],
 /// if project is an iOS project and such files do not already exist.
-void generateXcodeProperties(String projectPath) {
+Future<void> generateXcodeProperties(String projectPath) async {
   if (fs.isDirectorySync(fs.path.join(projectPath, 'ios'))) {
     if (fs.file(_generatedXcodePropertiesPath(projectPath)).existsSync())
       return;
-    updateGeneratedXcodeProperties(
+    await updateGeneratedXcodeProperties(
       projectPath: projectPath,
       buildInfo: BuildInfo.debug,
       targetOverride: bundle.defaultMainPath,
@@ -47,12 +51,12 @@
 ///
 /// targetOverride: Optional parameter, if null or unspecified the default value
 /// from xcode_backend.sh is used 'lib/main.dart'.
-void updateGeneratedXcodeProperties({
+Future<void> updateGeneratedXcodeProperties({
   @required String projectPath,
   @required BuildInfo buildInfo,
   String targetOverride,
   @required bool previewDart2,
-}) {
+}) async {
   final StringBuffer localsBuffer = new StringBuffer();
 
   localsBuffer.writeln('// This is a generated file; do not edit or check into version control.');
@@ -77,6 +81,24 @@
 
   localsBuffer.writeln('FLUTTER_FRAMEWORK_DIR=${flutterFrameworkDir(buildInfo.mode)}');
 
+  final String flutterManifest = fs.path.join(projectPath, bundle.defaultManifestPath);
+  FlutterManifest manifest;
+  try {
+    manifest = await FlutterManifest.createFromPath(flutterManifest);
+  } catch (error) {
+    throwToolExit('Failed to load pubspec.yaml: $error');
+  }
+
+  final String buildName = buildInfo?.buildName ?? manifest.buildName;
+  if (buildName != null) {
+    localsBuffer.writeln('FLUTTER_BUILD_NAME=$buildName');
+  }
+
+  final int buildNumber = buildInfo?.buildNumber ?? manifest.buildNumber;
+  if (buildNumber != null) {
+    localsBuffer.writeln('FLUTTER_BUILD_NUMBER=$buildNumber');
+  }
+
   if (artifacts is LocalEngineArtifacts) {
     final LocalEngineArtifacts localEngineArtifacts = artifacts;
     localsBuffer.writeln('LOCAL_ENGINE=${localEngineArtifacts.engineOutPath}');
diff --git a/packages/flutter_tools/lib/src/project.dart b/packages/flutter_tools/lib/src/project.dart
index 76056d7..f1d2b94 100644
--- a/packages/flutter_tools/lib/src/project.dart
+++ b/packages/flutter_tools/lib/src/project.dart
@@ -55,12 +55,12 @@
 
   /// Generates project files necessary to make Gradle builds work on Android
   /// and CocoaPods+Xcode work on iOS, for app projects only
-  void ensureReadyForPlatformSpecificTooling() {
+  Future<void> ensureReadyForPlatformSpecificTooling() async {
     if (!directory.existsSync() || hasExampleApp) {
       return;
     }
     injectPlugins(directory: directory.path);
-    generateXcodeProperties(directory.path);
+    await generateXcodeProperties(directory.path);
   }
 }
 
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index e707d7c..2699a77 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -329,7 +329,7 @@
 
     if (shouldRunPub) {
       await pubGet(context: PubContext.getVerifyContext(name));
-      new FlutterProject(fs.currentDirectory).ensureReadyForPlatformSpecificTooling();
+      await new FlutterProject(fs.currentDirectory).ensureReadyForPlatformSpecificTooling();
     }
 
     setupApplicationPackages();
diff --git a/packages/flutter_tools/templates/create/android-java.tmpl/app/build.gradle.tmpl b/packages/flutter_tools/templates/create/android-java.tmpl/app/build.gradle.tmpl
index 868a8e2..dffea9a 100644
--- a/packages/flutter_tools/templates/create/android-java.tmpl/app/build.gradle.tmpl
+++ b/packages/flutter_tools/templates/create/android-java.tmpl/app/build.gradle.tmpl
@@ -11,6 +11,16 @@
     throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
 }
 
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    throw new GradleException("versionCode not found. Define flutter.versionCode in the local.properties file.")
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.")
+}
+
 apply plugin: 'com.android.application'
 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
 
@@ -26,8 +36,8 @@
         applicationId "{{androidIdentifier}}"
         minSdkVersion 16
         targetSdkVersion 27
-        versionCode 1
-        versionName "1.0"
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
 
diff --git a/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/build.gradle.tmpl b/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/build.gradle.tmpl
index ef35e7e..db8cdb6 100644
--- a/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/build.gradle.tmpl
+++ b/packages/flutter_tools/templates/create/android-kotlin.tmpl/app/build.gradle.tmpl
@@ -11,6 +11,16 @@
     throw new GradleException("Flutter SDK not found. Define location with flutter.sdk in the local.properties file.")
 }
 
+def flutterVersionCode = localProperties.getProperty('flutter.versionCode')
+if (flutterVersionCode == null) {
+    throw new GradleException("versionCode not found. Define flutter.versionCode in the local.properties file.")
+}
+
+def flutterVersionName = localProperties.getProperty('flutter.versionName')
+if (flutterVersionName == null) {
+    throw new GradleException("versionName not found. Define flutter.versionName in the local.properties file.")
+}
+
 apply plugin: 'com.android.application'
 apply plugin: 'kotlin-android'
 apply from: "$flutterRoot/packages/flutter_tools/gradle/flutter.gradle"
@@ -31,8 +41,8 @@
         applicationId "{{androidIdentifier}}"
         minSdkVersion 16
         targetSdkVersion 27
-        versionCode 1
-        versionName "1.0"
+        versionCode flutterVersionCode.toInteger()
+        versionName flutterVersionName
         testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
     }
 
diff --git a/packages/flutter_tools/templates/create/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/create/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
index 30edc23..5cd8e04 100644
--- a/packages/flutter_tools/templates/create/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
+++ b/packages/flutter_tools/templates/create/ios-objc.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
@@ -370,7 +370,7 @@
 			baseConfigurationReference = 9740EEB21CF90195004384FC /* Debug.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
@@ -393,7 +393,7 @@
 			baseConfigurationReference = 7AFA3C8E1D35360C0083082E /* Release.xcconfig */;
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
diff --git a/packages/flutter_tools/templates/create/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl b/packages/flutter_tools/templates/create/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
index 2363bd5..1187089 100644
--- a/packages/flutter_tools/templates/create/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
+++ b/packages/flutter_tools/templates/create/ios-swift.tmpl/Runner.xcodeproj/project.pbxproj.tmpl
@@ -368,7 +368,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
@@ -396,7 +396,7 @@
 			buildSettings = {
 				ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
 				CLANG_ENABLE_MODULES = YES;
-				CURRENT_PROJECT_VERSION = 1;
+				CURRENT_PROJECT_VERSION = "$(FLUTTER_BUILD_NUMBER)";
 				ENABLE_BITCODE = NO;
 				FRAMEWORK_SEARCH_PATHS = (
 					"$(inherited)",
diff --git a/packages/flutter_tools/templates/create/ios.tmpl/Runner/Info.plist.tmpl b/packages/flutter_tools/templates/create/ios.tmpl/Runner/Info.plist.tmpl
index 5872326..3a4012f 100644
--- a/packages/flutter_tools/templates/create/ios.tmpl/Runner/Info.plist.tmpl
+++ b/packages/flutter_tools/templates/create/ios.tmpl/Runner/Info.plist.tmpl
@@ -15,11 +15,11 @@
 	<key>CFBundlePackageType</key>
 	<string>APPL</string>
 	<key>CFBundleShortVersionString</key>
-	<string>1.0</string>
+	<string>$(FLUTTER_BUILD_NAME)</string>
 	<key>CFBundleSignature</key>
 	<string>????</string>
 	<key>CFBundleVersion</key>
-	<string>1</string>
+	<string>$(FLUTTER_BUILD_NUMBER)</string>
 	<key>LSRequiresIPhoneOS</key>
 	<true/>
 	<key>UILaunchStoryboardName</key>
diff --git a/packages/flutter_tools/templates/create/pubspec.yaml.tmpl b/packages/flutter_tools/templates/create/pubspec.yaml.tmpl
index d469b19..807cbfa 100644
--- a/packages/flutter_tools/templates/create/pubspec.yaml.tmpl
+++ b/packages/flutter_tools/templates/create/pubspec.yaml.tmpl
@@ -1,6 +1,14 @@
 name: {{projectName}}
 description: {{description}}
 
+# The following defines the version and build number for your application.
+# A version number is three numbers separated by dots, like 1.2.43
+# followed by an optional build number separated by a +.
+# Both the version and the builder number may be overridden in flutter
+# build by specifying --build-name and --build-number, respectively.
+# Read more about versioning at semver.org.
+version: 1.0.0+1
+
 dependencies:
   flutter:
     sdk: flutter
diff --git a/packages/flutter_tools/test/android/gradle_test.dart b/packages/flutter_tools/test/android/gradle_test.dart
index 5da37d5..c03af96 100644
--- a/packages/flutter_tools/test/android/gradle_test.dart
+++ b/packages/flutter_tools/test/android/gradle_test.dart
@@ -2,16 +2,20 @@
 // 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:flutter_tools/src/android/gradle.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/cache.dart';
 import 'package:test/test.dart';
 
 import '../src/common.dart';
+import '../src/context.dart';
 
 void main() {
   group('gradle build', () {
-    test('do not crash if there is no Android SDK', () {
+    test('do not crash if there is no Android SDK', () async {
       Exception shouldBeToolExit;
       try {
         // We'd like to always set androidSdk to null and test updateLocalProperties. But that's
@@ -21,7 +25,7 @@
         // This test is written to fail if our bots get Android SDKs in the future: shouldBeToolExit
         // will be null and our expectation would fail. That would remind us to make these tests
         // hermetic before adding Android SDKs to the bots.
-        updateLocalProperties();
+        await updateLocalProperties();
       } on Exception catch (e) {
         shouldBeToolExit = e;
       }
@@ -126,4 +130,181 @@
       expect(project.assembleTaskFor(const BuildInfo(BuildMode.release, 'unknown')), isNull);
     });
   });
+
+  group('Gradle local.properties', () {
+    Directory temp;
+
+    setUp(() {
+      Cache.disableLocking();
+      temp = fs.systemTempDirectory.createTempSync('flutter_tools');
+    });
+
+    tearDown(() {
+      temp.deleteSync(recursive: true);
+    });
+
+    Future<String> createMinimalProject(String manifest) async {
+      final Directory directory = temp.childDirectory('android_project');
+      final File manifestFile = directory.childFile('pubspec.yaml');
+      manifestFile.createSync(recursive: true);
+      manifestFile.writeAsStringSync(manifest);
+
+      return directory.path;
+    }
+
+    String propertyFor(String key, File file) {
+      return file
+          .readAsLinesSync()
+          .where((String line) => line.startsWith('$key='))
+          .map((String line) => line.split('=')[1])
+          .first;
+    }
+
+    Future<void> checkBuildVersion({
+      String manifest,
+      BuildInfo buildInfo,
+      String expectedBuildName,
+      String expectedBuildNumber,
+    }) async {
+      final String projectPath = await createMinimalProject(manifest);
+
+      try {
+        await updateLocalProperties(projectPath: projectPath, buildInfo: buildInfo);
+
+        final String propertiesPath = fs.path.join(projectPath, 'android', 'local.properties');
+        final File localPropertiesFile = fs.file(propertiesPath);
+
+        expect(propertyFor('flutter.versionName', localPropertiesFile), expectedBuildName);
+        expect(propertyFor('flutter.versionCode', localPropertiesFile), expectedBuildNumber);
+      } on Exception {
+        // Android SDK not found, skip test
+      }
+    }
+
+    testUsingContext('extract build name and number from pubspec.yaml', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0+1
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.0',
+        expectedBuildNumber: '1',
+      );
+    });
+
+    testUsingContext('extract build name from pubspec.yaml', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.0',
+        expectedBuildNumber: null,
+      );
+    });
+
+    testUsingContext('allow build info to override build name', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0+1
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2');
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.2',
+        expectedBuildNumber: '1',
+      );
+    });
+
+    testUsingContext('allow build info to override build number', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0+1
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildNumber: 3);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.0',
+        expectedBuildNumber: '3',
+      );
+    });
+
+    testUsingContext('allow build info to override build name and number', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0+1
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.2',
+        expectedBuildNumber: '3',
+      );
+    });
+
+    testUsingContext('allow build info to override build name and set number', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.2',
+        expectedBuildNumber: '3',
+      );
+    });
+
+    testUsingContext('allow build info to set build name and number', () async {
+      const String manifest = '''
+name: test
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.2',
+        expectedBuildNumber: '3',
+      );
+    });
+  });
 }
diff --git a/packages/flutter_tools/test/flutter_manifest_test.dart b/packages/flutter_tools/test/flutter_manifest_test.dart
index 75be31c..91c42cc 100644
--- a/packages/flutter_tools/test/flutter_manifest_test.dart
+++ b/packages/flutter_tools/test/flutter_manifest_test.dart
@@ -2,6 +2,8 @@
 // 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 'package:file/memory.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
@@ -359,6 +361,118 @@
       final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest);
       expect(flutterManifest.isEmpty, false);
     });
+
+    Future<void> checkManifestVersion({
+      String manifest,
+      String expectedAppVersion,
+      String expectedBuildName,
+      int expectedBuildNumber,
+    }) async {
+      final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest);
+      expect(flutterManifest.appVersion, expectedAppVersion);
+      expect(flutterManifest.buildName, expectedBuildName);
+      expect(flutterManifest.buildNumber, expectedBuildNumber);
+    }
+
+    test('parses major.minor.patch+build version clause', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0+2
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      await checkManifestVersion(
+        manifest: manifest,
+        expectedAppVersion: '1.0.0+2',
+        expectedBuildName: '1.0.0',
+        expectedBuildNumber: 2,
+      );
+    });
+
+    test('parses major.minor+build version clause', () async {
+      const String manifest = '''
+name: test
+version: 1.0+2
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      await checkManifestVersion(
+        manifest: manifest,
+        expectedAppVersion: '1.0+2',
+        expectedBuildName: '1.0',
+        expectedBuildNumber: 2,
+      );
+    });
+
+    test('parses major+build version clause', () async {
+      const String manifest = '''
+name: test
+version: 1+2
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      await checkManifestVersion(
+        manifest: manifest,
+        expectedAppVersion: '1+2',
+        expectedBuildName: '1',
+        expectedBuildNumber: 2,
+      );
+    });
+
+    test('parses major version clause', () async {
+      const String manifest = '''
+name: test
+version: 1
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      await checkManifestVersion(
+        manifest: manifest,
+        expectedAppVersion: '1',
+        expectedBuildName: '1',
+        expectedBuildNumber: null,
+      );
+    });
+
+    test('parses empty version clause', () async {
+      const String manifest = '''
+name: test
+version:
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      await checkManifestVersion(
+        manifest: manifest,
+        expectedAppVersion: null,
+        expectedBuildName: null,
+        expectedBuildNumber: null,
+      );
+    });
+    test('parses no version clause', () async {
+      const String manifest = '''
+name: test
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      await checkManifestVersion(
+        manifest: manifest,
+        expectedAppVersion: null,
+        expectedBuildName: null,
+        expectedBuildNumber: null,
+      );
+    });
   });
 
   group('FlutterManifest with MemoryFileSystem', () {
@@ -371,8 +485,7 @@
 flutter:
 ''';
 
-      final FlutterManifest flutterManifest = await FlutterManifest
-          .createFromString(manifest);
+      final FlutterManifest flutterManifest = await FlutterManifest.createFromString(manifest);
       expect(flutterManifest.isEmpty, false);
     }
 
diff --git a/packages/flutter_tools/test/ios/xcodeproj_test.dart b/packages/flutter_tools/test/ios/xcodeproj_test.dart
index dd75f93..95f74ba 100644
--- a/packages/flutter_tools/test/ios/xcodeproj_test.dart
+++ b/packages/flutter_tools/test/ios/xcodeproj_test.dart
@@ -2,11 +2,15 @@
 // 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/memory.dart';
 import 'package:flutter_tools/src/artifacts.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
 import 'package:flutter_tools/src/base/io.dart';
 import 'package:flutter_tools/src/build_info.dart';
+import 'package:flutter_tools/src/bundle.dart' as bundle;
+import 'package:flutter_tools/src/cache.dart';
 import 'package:flutter_tools/src/ios/xcodeproj.dart';
 import 'package:mockito/mockito.dart';
 import 'package:platform/platform.dart';
@@ -277,14 +281,14 @@
       });
     }
 
-    testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () {
+    testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () async {
       when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine');
       when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile_arm'));
       const BuildInfo buildInfo = const BuildInfo(BuildMode.debug, null,
         previewDart2: true,
         targetPlatform: TargetPlatform.ios,
       );
-      updateGeneratedXcodeProperties(
+      await updateGeneratedXcodeProperties(
         projectPath: 'path/to/project',
         buildInfo: buildInfo,
         previewDart2: true,
@@ -297,14 +301,14 @@
       expect(contents.contains('ARCHS=armv7'), isTrue);
     });
 
-    testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () {
+    testUsingOsxContext('sets ARCHS=armv7 when armv7 local engine is set', () async {
       when(mockArtifacts.getArtifactPath(Artifact.flutterFramework, TargetPlatform.ios, any)).thenReturn('engine');
       when(mockArtifacts.engineOutPath).thenReturn(fs.path.join('out', 'ios_profile'));
       const BuildInfo buildInfo = const BuildInfo(BuildMode.debug, null,
         previewDart2: true,
         targetPlatform: TargetPlatform.ios,
       );
-      updateGeneratedXcodeProperties(
+      await updateGeneratedXcodeProperties(
         projectPath: 'path/to/project',
         buildInfo: buildInfo,
         previewDart2: true,
@@ -317,6 +321,185 @@
       expect(contents.contains('ARCHS=arm64'), isTrue);
     });
   });
+
+  group('Xcode Generated.xcconfig', () {
+    Directory temp;
+
+    setUp(() {
+      Cache.disableLocking();
+      temp = fs.systemTempDirectory.createTempSync('flutter_tools');
+    });
+
+    tearDown(() {
+      temp.deleteSync(recursive: true);
+    });
+
+    Future<String> createMinimalProject(String manifest) async {
+      final Directory directory = temp.childDirectory('ios_project');
+      final File manifestFile = directory.childFile('pubspec.yaml');
+      manifestFile.createSync(recursive: true);
+      manifestFile.writeAsStringSync(manifest);
+
+      return directory.path;
+    }
+
+    String propertyFor(String key, File file) {
+      final List<String> properties = file
+          .readAsLinesSync()
+          .where((String line) => line.startsWith('$key='))
+          .map((String line) => line.split('=')[1])
+          .toList();
+      return properties.isEmpty ? null : properties.first;
+    }
+
+    Future<void> checkBuildVersion({
+      String manifest,
+      BuildInfo buildInfo,
+      String expectedBuildName,
+      String expectedBuildNumber,
+    }) async {
+      final String projectPath = await createMinimalProject(manifest);
+
+      await updateGeneratedXcodeProperties(
+        projectPath: projectPath,
+        buildInfo: buildInfo,
+        targetOverride: bundle.defaultMainPath,
+        previewDart2: false,
+      );
+
+      final String propertiesPath = fs.path.join(projectPath, 'ios', 'Flutter', 'Generated.xcconfig');
+      final File localPropertiesFile = fs.file(propertiesPath);
+
+      expect(propertyFor('FLUTTER_BUILD_NAME', localPropertiesFile), expectedBuildName);
+      expect(propertyFor('FLUTTER_BUILD_NUMBER', localPropertiesFile), expectedBuildNumber);
+    }
+
+    testUsingContext('extract build name and number from pubspec.yaml', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0+1
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.0',
+        expectedBuildNumber: '1',
+      );
+    });
+
+    testUsingContext('extract build name from pubspec.yaml', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.0',
+        expectedBuildNumber: null,
+      );
+    });
+
+    testUsingContext('allow build info to override build name', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0+1
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2');
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.2',
+        expectedBuildNumber: '1',
+      );
+    });
+
+    testUsingContext('allow build info to override build number', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0+1
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildNumber: 3);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.0',
+        expectedBuildNumber: '3',
+      );
+    });
+
+    testUsingContext('allow build info to override build name and number', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0+1
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.2',
+        expectedBuildNumber: '3',
+      );
+    });
+
+    testUsingContext('allow build info to override build name and set number', () async {
+      const String manifest = '''
+name: test
+version: 1.0.0
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.2',
+        expectedBuildNumber: '3',
+      );
+    });
+
+    testUsingContext('allow build info to set build name and number', () async {
+      const String manifest = '''
+name: test
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = const BuildInfo(BuildMode.release, null, buildName: '1.0.2', buildNumber: 3);
+      await checkBuildVersion(
+        manifest: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.2',
+        expectedBuildNumber: '3',
+      );
+    });
+  });
 }
 
 Platform fakePlatform(String name) {
diff --git a/packages/flutter_tools/test/project_test.dart b/packages/flutter_tools/test/project_test.dart
index 632018d..8955187 100644
--- a/packages/flutter_tools/test/project_test.dart
+++ b/packages/flutter_tools/test/project_test.dart
@@ -20,23 +20,23 @@
     group('ensure ready for platform-specific tooling', () {
       testInMemory('does nothing, if project is not created', () async {
         final FlutterProject project = someProject();
-        project.ensureReadyForPlatformSpecificTooling();
+        await project.ensureReadyForPlatformSpecificTooling();
         expect(project.directory.existsSync(), isFalse);
       });
       testInMemory('does nothing in plugin or package root project', () async {
         final FlutterProject project = aPluginProject();
-        project.ensureReadyForPlatformSpecificTooling();
+        await project.ensureReadyForPlatformSpecificTooling();
         expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isFalse);
         expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isFalse);
       });
       testInMemory('injects plugins', () async {
         final FlutterProject project = aProjectWithIos();
-        project.ensureReadyForPlatformSpecificTooling();
+        await project.ensureReadyForPlatformSpecificTooling();
         expect(project.ios.directory.childFile('Runner/GeneratedPluginRegistrant.h').existsSync(), isTrue);
       });
       testInMemory('generates Xcode configuration', () async {
         final FlutterProject project = aProjectWithIos();
-        project.ensureReadyForPlatformSpecificTooling();
+        await project.ensureReadyForPlatformSpecificTooling();
         expect(project.ios.directory.childFile('Flutter/Generated.xcconfig').existsSync(), isTrue);
       });
     });