Refactor build-number/build-name logic. (#27743)

This PR aims at several things:

1. Use pub_semver to check a version in pubspec.yaml meets the requirements specified in https://semver.org/.
2. Don't limit build-number/build-name as a fixed format. Instead, validate it according to the target(ios/android).
3. Make sure that build-number/build-name are always validated no matter it's specified by the `flutter command` or version in pubspec.yaml.

Fixes #27589
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index 0e63342..cd8ce05 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -83,7 +83,7 @@
   /// It is used to determine whether one build is more recent than another, with higher numbers indicating more recent build.
   /// On Android it is used as versionCode.
   /// On Xcode builds it is used as CFBundleVersion.
-  final int buildNumber;
+  final String buildNumber;
 
   /// A "x.y.z" string used as the version number shown to users.
   /// For each new version of your app, you will provide a version number to differentiate it from previous versions.
@@ -141,6 +141,83 @@
   dynamicRelease
 }
 
+String validatedBuildNumberForPlatform(TargetPlatform targetPlatform, String buildNumber) {
+  if (buildNumber == null) {
+    return null;
+  }
+  if (targetPlatform == TargetPlatform.ios ||
+      targetPlatform == TargetPlatform.darwin_x64) {
+    // 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, '');
+    final List<String> segments = tmpBuildNumber
+        .split('.')
+        .where((String segment) => segment.isNotEmpty)
+        .toList();
+    if (segments.isEmpty) {
+      segments.add('0');
+    }
+    tmpBuildNumber = segments.join('.');
+    if (tmpBuildNumber != buildNumber) {
+      printTrace('Invalid build-number: $buildNumber for iOS/macOS, overridden by $tmpBuildNumber.\n'
+          'See CFBundleVersion at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html');
+    }
+    return tmpBuildNumber;
+  }
+  if (targetPlatform == TargetPlatform.android_arm ||
+      targetPlatform == TargetPlatform.android_arm64 ||
+      targetPlatform == TargetPlatform.android_x64 ||
+      targetPlatform == TargetPlatform.android_x86) {
+    // See versionCode at https://developer.android.com/studio/publish/versioning
+    final RegExp disallowed = RegExp(r'[^\d]');
+    String tmpBuildNumberStr = buildNumber.replaceAll(disallowed, '');
+    int tmpBuildNumberInt = int.tryParse(tmpBuildNumberStr) ?? 0;
+    if (tmpBuildNumberInt < 1) {
+      tmpBuildNumberInt = 1;
+    }
+    tmpBuildNumberStr = tmpBuildNumberInt.toString();
+    if (tmpBuildNumberStr != buildNumber) {
+      printTrace('Invalid build-number: $buildNumber for Android, overridden by $tmpBuildNumberStr.\n'
+          'See versionCode at https://developer.android.com/studio/publish/versioning');
+    }
+    return tmpBuildNumberStr;
+  }
+  return buildNumber;
+}
+
+String validatedBuildNameForPlatform(TargetPlatform targetPlatform, String buildName) {
+  if (buildName == null) {
+    return null;
+  }
+  if (targetPlatform == TargetPlatform.ios ||
+      targetPlatform == TargetPlatform.darwin_x64) {
+    // 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, '');
+    final List<String> segments = tmpBuildName
+        .split('.')
+        .where((String segment) => segment.isNotEmpty)
+        .toList();
+    while (segments.length < 3) {
+      segments.add('0');
+    }
+    tmpBuildName = segments.join('.');
+    if (tmpBuildName != buildName) {
+      printTrace('Invalid build-name: $buildName for iOS/macOS, overridden by $tmpBuildName.\n'
+          'See CFBundleShortVersionString at https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html');
+    }
+    return tmpBuildName;
+  }
+  if (targetPlatform == TargetPlatform.android_arm ||
+      targetPlatform == TargetPlatform.android_arm64 ||
+      targetPlatform == TargetPlatform.android_x64 ||
+      targetPlatform == TargetPlatform.android_x86) {
+    // See versionName at https://developer.android.com/studio/publish/versioning
+    return buildName;
+  }
+  return buildName;
+}
+
 String getModeName(BuildMode mode) => getEnumName(mode);
 
 String getFriendlyModeName(BuildMode mode) {