Warn when build number and version can't be parsed on iOS (#40611)

diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 57ee601..fefa80c 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -152,6 +152,9 @@
     // See CFBundleVersion at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
     final RegExp disallowed = RegExp(r'[^\d\.]');
     String tmpBuildNumber = buildNumber.replaceAll(disallowed, '');
+    if (tmpBuildNumber.isEmpty) {
+      return null;
+    }
     final List<String> segments = tmpBuildNumber
         .split('.')
         .where((String segment) => segment.isNotEmpty)
@@ -196,6 +199,9 @@
     // See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
     final RegExp disallowed = RegExp(r'[^\d\.]');
     String tmpBuildName = buildName.replaceAll(disallowed, '');
+    if (tmpBuildName.isEmpty) {
+      return null;
+    }
     final List<String> segments = tmpBuildName
         .split('.')
         .where((String segment) => segment.isNotEmpty)
diff --git a/packages/flutter_tools/lib/src/ios/xcodeproj.dart b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
index 4b81503..785f213 100644
--- a/packages/flutter_tools/lib/src/ios/xcodeproj.dart
+++ b/packages/flutter_tools/lib/src/ios/xcodeproj.dart
@@ -159,20 +159,52 @@
   }
 
   final String buildNameToParse = buildInfo?.buildName ?? project.manifest.buildName;
-  final String buildName = validatedBuildNameForPlatform(TargetPlatform.ios, buildNameToParse);
-  if (buildName != null) {
-    xcodeBuildSettings.add('FLUTTER_BUILD_NAME=$buildName');
+  final bool buildNameIsMissing = buildNameToParse == null || buildNameToParse.isEmpty;
+  String buildName;
+  const String defaultBuildName = '1.0.0';
+  if (buildNameIsMissing) {
+    buildName = defaultBuildName;
+  } else {
+    buildName = validatedBuildNameForPlatform(TargetPlatform.ios, buildNameToParse);
   }
 
-  String buildNumber = validatedBuildNumberForPlatform(TargetPlatform.ios, buildInfo?.buildNumber ?? project.manifest.buildNumber);
-  // Drop back to parsing build name if build number is not present. Build number is optional in the manifest, but
-  // FLUTTER_BUILD_NUMBER is required as the backing value for the required CFBundleVersion.
-  buildNumber ??= validatedBuildNumberForPlatform(TargetPlatform.ios, buildNameToParse);
+  final String buildNumberToParse = buildInfo?.buildNumber ?? project.manifest.buildNumber;
+  final bool buildNumberIsMissing =
+      (buildNumberToParse == null || buildNumberToParse.isEmpty) && buildNameIsMissing;
+  String buildNumber;
 
-  if (buildNumber != null) {
-    xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber');
+  const String defaultBuildNumber = '1';
+  if (buildNumberIsMissing) {
+    buildNumber = defaultBuildNumber;
+  } else {
+    buildNumber = validatedBuildNumberForPlatform(TargetPlatform.ios, buildNumberToParse);
+    // Drop back to parsing build name if build number is not present. Build number is optional in the manifest, but
+    // FLUTTER_BUILD_NUMBER is required as the backing value for the required CFBundleVersion.
+    buildNumber ??= validatedBuildNumberForPlatform(TargetPlatform.ios, buildNameToParse);
   }
 
+  if (buildNameIsMissing) {
+    printError('Warning: Missing build name (CFBundleShortVersionString), defaulting to $defaultBuildName.');
+  }
+  if (buildNumberIsMissing) {
+    printError('Warning: Missing build number (CFBundleVersion), defaulting to $defaultBuildNumber.');
+  }
+  if (buildNameIsMissing || buildNumberIsMissing) {
+    printError('Action Required: You must set a build name and number in the pubspec.yaml '
+               'file version field before submitting to the App Store.');
+  }
+
+  if (buildName == null && buildNumber == null) {
+    throwToolExit('Cannot parse build number $buildNumberToParse or build name $buildNameToParse, check pubspec.yaml version.');
+  } else if (buildName == null) {
+    throwToolExit('Cannot parse build name $buildNameToParse, check pubspec.yaml version.');
+  } else if (buildNumber == null) {
+    throwToolExit('Cannot parse build number $buildNumberToParse, check pubspec.yaml version.');
+  }
+
+  xcodeBuildSettings.add('FLUTTER_BUILD_NAME=$buildName');
+  xcodeBuildSettings.add('FLUTTER_BUILD_NUMBER=$buildNumber');
+
   if (artifacts is LocalEngineArtifacts) {
     final LocalEngineArtifacts localEngineArtifacts = artifacts;
     final String engineOutPath = localEngineArtifacts.engineOutPath;
diff --git a/packages/flutter_tools/test/general.shard/build_info_test.dart b/packages/flutter_tools/test/general.shard/build_info_test.dart
index 1f12d08..d404696 100644
--- a/packages/flutter_tools/test/general.shard/build_info_test.dart
+++ b/packages/flutter_tools/test/general.shard/build_info_test.dart
@@ -16,7 +16,7 @@
 
     testUsingContext('CFBundleVersion for iOS', () async {
       String buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, 'xyz');
-      expect(buildName, '0');
+      expect(buildName, isNull);
       buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, '0.0.1');
       expect(buildName, '0.0.1');
       buildName = validatedBuildNumberForPlatform(TargetPlatform.ios, '123.xyz');
@@ -38,7 +38,7 @@
 
     testUsingContext('CFBundleShortVersionString for iOS', () async {
       String buildName = validatedBuildNameForPlatform(TargetPlatform.ios, 'xyz');
-      expect(buildName, '0.0.0');
+      expect(buildName, isNull);
       buildName = validatedBuildNameForPlatform(TargetPlatform.ios, '0.0.1');
       expect(buildName, '0.0.1');
       buildName = validatedBuildNameForPlatform(TargetPlatform.ios, '123.456.xyz');
diff --git a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
index 9e1b8ae..10765df 100644
--- a/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
+++ b/packages/flutter_tools/test/general.shard/ios/xcodeproj_test.dart
@@ -629,6 +629,41 @@
         expectedBuildNumber: '3',
       );
     });
+
+    testUsingOsxContext('default build name and number when version is missing', () async {
+      const String manifest = '''
+name: test
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null);
+      await checkBuildVersion(
+        manifestString: manifest,
+        buildInfo: buildInfo,
+        expectedBuildName: '1.0.0',
+        expectedBuildNumber: '1',
+      );
+    });
+
+    testUsingOsxContext('fail when build name cannot be parsed', () async {
+      const String manifest = '''
+name: test
+dependencies:
+  flutter:
+    sdk: flutter
+flutter:
+''';
+      const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, buildName: 'abc', buildNumber: '1');
+
+      const String stderr = 'Cannot parse build name abc, check pubspec.yaml version.';
+      expect(() async => await checkBuildVersion(
+        manifestString: manifest,
+        buildInfo: buildInfo
+      ),
+      throwsToolExit(message: stderr));
+    });
   });
 }