[Windows] Flow version information to the build output (#106145)

Previously developers had to edit their `Runner.rc` file to update their executable's version information. Now, version information will automatically be set from `flutter build`'s arguments or the `pubspec.yaml` file for new projects. 

Addresses https://github.com/flutter/flutter/issues/73652
diff --git a/dev/integration_tests/flutter_gallery/windows/runner/CMakeLists.txt b/dev/integration_tests/flutter_gallery/windows/runner/CMakeLists.txt
index de2d891..17411a8 100644
--- a/dev/integration_tests/flutter_gallery/windows/runner/CMakeLists.txt
+++ b/dev/integration_tests/flutter_gallery/windows/runner/CMakeLists.txt
@@ -1,6 +1,11 @@
 cmake_minimum_required(VERSION 3.14)
 project(runner LANGUAGES CXX)
 
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
 add_executable(${BINARY_NAME} WIN32
   "flutter_window.cpp"
   "main.cpp"
@@ -10,8 +15,25 @@
   "Runner.rc"
   "runner.exe.manifest"
 )
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
 apply_standard_settings(${BINARY_NAME})
+
+# Add preprocessor definitions for the build version.
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
+
+# Disable Windows macros that collide with C++ standard library functions.
 target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+
+# Add dependency libraries and include directories. Add any application-specific
+# dependencies here.
 target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
 target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+
+# Run the Flutter tool portions of the build. This must not be removed.
 add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/dev/integration_tests/flutter_gallery/windows/runner/Runner.rc b/dev/integration_tests/flutter_gallery/windows/runner/Runner.rc
index 4ed0d4a..79f936a 100644
--- a/dev/integration_tests/flutter_gallery/windows/runner/Runner.rc
+++ b/dev/integration_tests/flutter_gallery/windows/runner/Runner.rc
@@ -60,14 +60,14 @@
 // Version
 //
 
-#ifdef FLUTTER_BUILD_NUMBER
-#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
 #else
-#define VERSION_AS_NUMBER 1,0,0
+#define VERSION_AS_NUMBER 1,0,0,0
 #endif
 
-#ifdef FLUTTER_BUILD_NAME
-#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
 #else
 #define VERSION_AS_STRING "1.0.0"
 #endif
diff --git a/dev/manual_tests/windows/runner/CMakeLists.txt b/dev/manual_tests/windows/runner/CMakeLists.txt
index de2d891..17411a8 100644
--- a/dev/manual_tests/windows/runner/CMakeLists.txt
+++ b/dev/manual_tests/windows/runner/CMakeLists.txt
@@ -1,6 +1,11 @@
 cmake_minimum_required(VERSION 3.14)
 project(runner LANGUAGES CXX)
 
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
 add_executable(${BINARY_NAME} WIN32
   "flutter_window.cpp"
   "main.cpp"
@@ -10,8 +15,25 @@
   "Runner.rc"
   "runner.exe.manifest"
 )
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
 apply_standard_settings(${BINARY_NAME})
+
+# Add preprocessor definitions for the build version.
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
+
+# Disable Windows macros that collide with C++ standard library functions.
 target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+
+# Add dependency libraries and include directories. Add any application-specific
+# dependencies here.
 target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
 target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+
+# Run the Flutter tool portions of the build. This must not be removed.
 add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/dev/manual_tests/windows/runner/Runner.rc b/dev/manual_tests/windows/runner/Runner.rc
index 1f4ecbc..7d70f92 100644
--- a/dev/manual_tests/windows/runner/Runner.rc
+++ b/dev/manual_tests/windows/runner/Runner.rc
@@ -60,14 +60,14 @@
 // Version
 //
 
-#ifdef FLUTTER_BUILD_NUMBER
-#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
 #else
-#define VERSION_AS_NUMBER 1,0,0
+#define VERSION_AS_NUMBER 1,0,0,0
 #endif
 
-#ifdef FLUTTER_BUILD_NAME
-#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
 #else
 #define VERSION_AS_STRING "1.0.0"
 #endif
diff --git a/examples/api/windows/runner/CMakeLists.txt b/examples/api/windows/runner/CMakeLists.txt
index de2d891..17411a8 100644
--- a/examples/api/windows/runner/CMakeLists.txt
+++ b/examples/api/windows/runner/CMakeLists.txt
@@ -1,6 +1,11 @@
 cmake_minimum_required(VERSION 3.14)
 project(runner LANGUAGES CXX)
 
+# Define the application target. To change its name, change BINARY_NAME in the
+# top-level CMakeLists.txt, not the value here, or `flutter run` will no longer
+# work.
+#
+# Any new source files that you add to the application should be added here.
 add_executable(${BINARY_NAME} WIN32
   "flutter_window.cpp"
   "main.cpp"
@@ -10,8 +15,25 @@
   "Runner.rc"
   "runner.exe.manifest"
 )
+
+# Apply the standard set of build settings. This can be removed for applications
+# that need different build settings.
 apply_standard_settings(${BINARY_NAME})
+
+# Add preprocessor definitions for the build version.
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
+
+# Disable Windows macros that collide with C++ standard library functions.
 target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
+
+# Add dependency libraries and include directories. Add any application-specific
+# dependencies here.
 target_link_libraries(${BINARY_NAME} PRIVATE flutter flutter_wrapper_app)
 target_include_directories(${BINARY_NAME} PRIVATE "${CMAKE_SOURCE_DIR}")
+
+# Run the Flutter tool portions of the build. This must not be removed.
 add_dependencies(${BINARY_NAME} flutter_assemble)
diff --git a/examples/api/windows/runner/Runner.rc b/examples/api/windows/runner/Runner.rc
index b8df251..34a087a 100644
--- a/examples/api/windows/runner/Runner.rc
+++ b/examples/api/windows/runner/Runner.rc
@@ -60,14 +60,14 @@
 // Version
 //
 
-#ifdef FLUTTER_BUILD_NUMBER
-#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
 #else
-#define VERSION_AS_NUMBER 1,0,0
+#define VERSION_AS_NUMBER 1,0,0,0
 #endif
 
-#ifdef FLUTTER_BUILD_NAME
-#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
 #else
 #define VERSION_AS_STRING "1.0.0"
 #endif
diff --git a/packages/flutter_tools/lib/src/build_info.dart b/packages/flutter_tools/lib/src/build_info.dart
index fe8ece7..9b28883 100644
--- a/packages/flutter_tools/lib/src/build_info.dart
+++ b/packages/flutter_tools/lib/src/build_info.dart
@@ -90,12 +90,14 @@
   /// 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.
+  /// On Windows it is used as the build suffix for the product and file versions.
   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.
   /// On Android it is used as versionName.
   /// On Xcode builds it is used as CFBundleShortVersionString.
+  /// On Windows it is used as the major, minor, and patch parts of the product and file versions.
   final String? buildName;
 
   /// An optional directory path to save debugging information from dwarf stack
diff --git a/packages/flutter_tools/lib/src/cmake.dart b/packages/flutter_tools/lib/src/cmake.dart
index 47b20f8..1a91d99 100644
--- a/packages/flutter_tools/lib/src/cmake.dart
+++ b/packages/flutter_tools/lib/src/cmake.dart
@@ -2,7 +2,11 @@
 // Use of this source code is governed by a BSD-style license that can be
 // found in the LICENSE file.
 
+import 'package:pub_semver/pub_semver.dart';
+
+import 'build_info.dart';
 import 'cmake_project.dart';
+import 'globals.dart' as globals;
 
 /// Extracts the `BINARY_NAME` from a project's CMake file.
 ///
@@ -25,19 +29,83 @@
   return s.replaceAll(r'\', r'\\');
 }
 
+String _determineVersionString(CmakeBasedProject project, BuildInfo buildInfo) {
+  // Prefer the build arguments for version information.
+  final String buildName = buildInfo.buildName ?? project.parent.manifest.buildName ?? '1.0.0';
+  final String? buildNumber = buildInfo.buildName != null
+    ? buildInfo.buildNumber
+    : (buildInfo.buildNumber ?? project.parent.manifest.buildNumber);
+
+  return buildNumber != null
+    ? '$buildName+$buildNumber'
+    : buildName;
+}
+
+Version _determineVersion(CmakeBasedProject project, BuildInfo buildInfo) {
+  final String version = _determineVersionString(project, buildInfo);
+  try {
+    return Version.parse(version);
+  } on FormatException {
+    globals.printWarning('Warning: could not parse version $version, defaulting to 1.0.0.');
+
+    return Version(1, 0, 0);
+  }
+}
+
+/// Attempts to map a Dart version's build identifier (the part after a +) into
+/// a single integer. Returns null for complex build identifiers like `foo` or `1.2`.
+int? _tryDetermineBuildVersion(Version version) {
+  if (version.build.isEmpty) {
+    return 0;
+  }
+
+  if (version.build.length != 1) {
+    return null;
+  }
+
+  final Object buildIdentifier = version.build.first as Object;
+  return buildIdentifier is int ? buildIdentifier : null;
+}
+
 /// Writes a generated CMake configuration file for [project], including
 /// variables expected by the build template and an environment variable list
 /// for calling back into Flutter.
-void writeGeneratedCmakeConfig(String flutterRoot, CmakeBasedProject project, Map<String, String> environment) {
+void writeGeneratedCmakeConfig(
+  String flutterRoot,
+  CmakeBasedProject project,
+  BuildInfo buildInfo,
+  Map<String, String> environment) {
   // Only a limited set of variables are needed by the CMake files themselves,
   // the rest are put into a list to pass to the re-entrant build step.
   final String escapedFlutterRoot = _escapeBackslashes(flutterRoot);
   final String escapedProjectDir = _escapeBackslashes(project.parent.directory.path);
+
+  final Version version = _determineVersion(project, buildInfo);
+  final int? buildVersion = _tryDetermineBuildVersion(version);
+
+  // Since complex Dart build identifiers cannot be converted into integers,
+  // different Dart versions may be converted into the same Windows numeric version.
+  // Warn the user as some Windows installers, like MSI, don't update files if their versions are equal.
+  if (buildVersion == null && project is WindowsProject) {
+      final String buildIdentifier = version.build.join('.');
+      globals.printWarning(
+        'Warning: build identifier $buildIdentifier in version $version is not numeric '
+        'and cannot be converted into a Windows build version number. Defaulting to 0.\n'
+        'This may cause issues with Windows installers.'
+      );
+  }
+
   final StringBuffer buffer = StringBuffer('''
 # Generated code do not commit.
 file(TO_CMAKE_PATH "$escapedFlutterRoot" FLUTTER_ROOT)
 file(TO_CMAKE_PATH "$escapedProjectDir" PROJECT_DIR)
 
+set(FLUTTER_VERSION "$version" PARENT_SCOPE)
+set(FLUTTER_VERSION_MAJOR ${version.major} PARENT_SCOPE)
+set(FLUTTER_VERSION_MINOR ${version.minor} PARENT_SCOPE)
+set(FLUTTER_VERSION_PATCH ${version.patch} PARENT_SCOPE)
+set(FLUTTER_VERSION_BUILD ${buildVersion ?? 0} PARENT_SCOPE)
+
 # Environment variables to pass to tool_backend.sh
 list(APPEND FLUTTER_TOOL_ENVIRONMENT
   "FLUTTER_ROOT=$escapedFlutterRoot"
diff --git a/packages/flutter_tools/lib/src/commands/build_linux.dart b/packages/flutter_tools/lib/src/commands/build_linux.dart
index adff5d7..9955a81 100644
--- a/packages/flutter_tools/lib/src/commands/build_linux.dart
+++ b/packages/flutter_tools/lib/src/commands/build_linux.dart
@@ -22,8 +22,6 @@
   }) : _operatingSystemUtils = operatingSystemUtils,
        super(verboseHelp: verboseHelp) {
     addCommonDesktopBuildOptions(verboseHelp: verboseHelp);
-    usesBuildNumberOption();
-    usesBuildNameOption();
     final String defaultTargetPlatform =
         (_operatingSystemUtils.hostPlatform == HostPlatform.linux_arm64) ?
             'linux-arm64' : 'linux-x64';
diff --git a/packages/flutter_tools/lib/src/commands/build_macos.dart b/packages/flutter_tools/lib/src/commands/build_macos.dart
index 415de8f..7150d66 100644
--- a/packages/flutter_tools/lib/src/commands/build_macos.dart
+++ b/packages/flutter_tools/lib/src/commands/build_macos.dart
@@ -19,8 +19,6 @@
     required bool verboseHelp,
   }) : super(verboseHelp: verboseHelp) {
     addCommonDesktopBuildOptions(verboseHelp: verboseHelp);
-    usesBuildNumberOption();
-    usesBuildNameOption();
   }
 
   @override
diff --git a/packages/flutter_tools/lib/src/linux/build_linux.dart b/packages/flutter_tools/lib/src/linux/build_linux.dart
index a1557c6..062fea3 100644
--- a/packages/flutter_tools/lib/src/linux/build_linux.dart
+++ b/packages/flutter_tools/lib/src/linux/build_linux.dart
@@ -62,7 +62,7 @@
     environmentConfig['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath));
     environmentConfig['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath);
   }
-  writeGeneratedCmakeConfig(Cache.flutterRoot!, linuxProject, environmentConfig);
+  writeGeneratedCmakeConfig(Cache.flutterRoot!, linuxProject, buildInfo, environmentConfig);
 
   createPluginSymlinks(linuxProject.parent);
 
diff --git a/packages/flutter_tools/lib/src/runner/flutter_command.dart b/packages/flutter_tools/lib/src/runner/flutter_command.dart
index c2f8d59..5f7e3dc 100644
--- a/packages/flutter_tools/lib/src/runner/flutter_command.dart
+++ b/packages/flutter_tools/lib/src/runner/flutter_command.dart
@@ -565,7 +565,8 @@
               'Each build must have a unique identifier to differentiate it from previous builds.\n'
               'It is used to determine whether one build is more recent than another, with higher numbers indicating more recent build.\n'
               'On Android it is used as "versionCode".\n'
-              'On Xcode builds it is used as "CFBundleVersion".',
+              'On Xcode builds it is used as "CFBundleVersion".\n'
+              'On Windows it is used as the build suffix for the product and file versions.',
     );
   }
 
@@ -574,7 +575,8 @@
         help: 'A "x.y.z" string used as the version number shown to users.\n'
               'For each new version of your app, you will provide a version number to differentiate it from previous versions.\n'
               'On Android it is used as "versionName".\n'
-              'On Xcode builds it is used as "CFBundleShortVersionString".',
+              'On Xcode builds it is used as "CFBundleShortVersionString".\n'
+              'On Windows it is used as the major, minor, and patch parts of the product and file versions.',
         valueHelp: 'x.y.z');
   }
 
@@ -893,6 +895,8 @@
     usesPubOption();
     usesTargetOption();
     usesTrackWidgetCreation(verboseHelp: verboseHelp);
+    usesBuildNumberOption();
+    usesBuildNameOption();
   }
 
   /// The build mode that this command will use if no build mode is
diff --git a/packages/flutter_tools/lib/src/windows/build_windows.dart b/packages/flutter_tools/lib/src/windows/build_windows.dart
index ed41e39..c38cf12 100644
--- a/packages/flutter_tools/lib/src/windows/build_windows.dart
+++ b/packages/flutter_tools/lib/src/windows/build_windows.dart
@@ -206,7 +206,7 @@
     environment['FLUTTER_ENGINE'] = globals.fs.path.dirname(globals.fs.path.dirname(engineOutPath));
     environment['LOCAL_ENGINE'] = globals.fs.path.basename(engineOutPath);
   }
-  writeGeneratedCmakeConfig(Cache.flutterRoot!, windowsProject, environment);
+  writeGeneratedCmakeConfig(Cache.flutterRoot!, windowsProject, buildInfo, environment);
 }
 
 // Works around the Visual Studio 17.1.0 CMake bug described in
diff --git a/packages/flutter_tools/templates/app/pubspec.yaml.tmpl b/packages/flutter_tools/templates/app/pubspec.yaml.tmpl
index a8ad36d..5939962 100644
--- a/packages/flutter_tools/templates/app/pubspec.yaml.tmpl
+++ b/packages/flutter_tools/templates/app/pubspec.yaml.tmpl
@@ -13,9 +13,11 @@
 # build by specifying --build-name and --build-number, respectively.
 # In Android, build-name is used as versionName while build-number used as versionCode.
 # Read more about Android versioning at https://developer.android.com/studio/publish/versioning
-# In iOS, build-name is used as CFBundleShortVersionString while build-number used as CFBundleVersion.
+# In iOS, build-name is used as CFBundleShortVersionString while build-number is used as CFBundleVersion.
 # Read more about iOS versioning at
 # https://developer.apple.com/library/archive/documentation/General/Reference/InfoPlistKeyReference/Articles/CoreFoundationKeys.html
+# In Windows, build-name is used as the major, minor, and patch parts
+# of the product and file versions while build-number is used as the build suffix.
 version: 1.0.0+1
 {{/withPlatformChannelPluginHook}}
 
diff --git a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/CMakeLists.txt b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/CMakeLists.txt
index b9e550f..17411a8 100644
--- a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/CMakeLists.txt
+++ b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/CMakeLists.txt
@@ -20,6 +20,13 @@
 # that need different build settings.
 apply_standard_settings(${BINARY_NAME})
 
+# Add preprocessor definitions for the build version.
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION=\"${FLUTTER_VERSION}\"")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MAJOR=${FLUTTER_VERSION_MAJOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_MINOR=${FLUTTER_VERSION_MINOR}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_PATCH=${FLUTTER_VERSION_PATCH}")
+target_compile_definitions(${BINARY_NAME} PRIVATE "FLUTTER_VERSION_BUILD=${FLUTTER_VERSION_BUILD}")
+
 # Disable Windows macros that collide with C++ standard library functions.
 target_compile_definitions(${BINARY_NAME} PRIVATE "NOMINMAX")
 
diff --git a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/Runner.rc.tmpl b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/Runner.rc.tmpl
index 7e4b8b7..26df327 100644
--- a/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/Runner.rc.tmpl
+++ b/packages/flutter_tools/templates/app_shared/windows.tmpl/runner/Runner.rc.tmpl
@@ -60,14 +60,14 @@
 // Version
 //
 
-#ifdef FLUTTER_BUILD_NUMBER
-#define VERSION_AS_NUMBER FLUTTER_BUILD_NUMBER
+#if defined(FLUTTER_VERSION_MAJOR) && defined(FLUTTER_VERSION_MINOR) && defined(FLUTTER_VERSION_PATCH) && defined(FLUTTER_VERSION_BUILD)
+#define VERSION_AS_NUMBER FLUTTER_VERSION_MAJOR,FLUTTER_VERSION_MINOR,FLUTTER_VERSION_PATCH,FLUTTER_VERSION_BUILD
 #else
-#define VERSION_AS_NUMBER 1,0,0
+#define VERSION_AS_NUMBER 1,0,0,0
 #endif
 
-#ifdef FLUTTER_BUILD_NAME
-#define VERSION_AS_STRING #FLUTTER_BUILD_NAME
+#if defined(FLUTTER_VERSION)
+#define VERSION_AS_STRING FLUTTER_VERSION
 #else
 #define VERSION_AS_STRING "1.0.0"
 #endif
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart
index 13a2956..bf8d2db 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/build_linux_test.dart
@@ -447,6 +447,11 @@
     expect(configLines, containsAll(<String>[
       'file(TO_CMAKE_PATH "$_kTestFlutterRoot" FLUTTER_ROOT)',
       'file(TO_CMAKE_PATH "${fileSystem.currentDirectory.path}" PROJECT_DIR)',
+      'set(FLUTTER_VERSION "1.0.0" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 0 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
       '  "DART_DEFINES=Zm9vLmJhcj0y,Zml6ei5mYXI9Mw=="',
       '  "DART_OBFUSCATION=true"',
       '  "EXTRA_FRONT_END_OPTIONS=--enable-experiment=non-nullable"',
diff --git a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart
index cfa19cb..cb1696b 100644
--- a/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart
+++ b/packages/flutter_tools/test/commands.shard/hermetic/build_windows_test.dart
@@ -472,6 +472,11 @@
     expect(configLines, containsAll(<String>[
       r'file(TO_CMAKE_PATH "C:\\flutter" FLUTTER_ROOT)',
       r'file(TO_CMAKE_PATH "C:\\" PROJECT_DIR)',
+      r'set(FLUTTER_VERSION "1.0.0" PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_PATCH 0 PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
       r'  "DART_DEFINES=Zm9vPWE=,YmFyPWI="',
       r'  "DART_OBFUSCATION=true"',
       r'  "EXTRA_FRONT_END_OPTIONS=--enable-experiment=non-nullable"',
@@ -535,6 +540,338 @@
     FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
   });
 
+  testUsingContext("Windows build uses pubspec's version", () async {
+    final FakeVisualStudio fakeVisualStudio = FakeVisualStudio();
+    final BuildWindowsCommand command = BuildWindowsCommand()
+      ..visualStudioOverride = fakeVisualStudio;
+    setUpMockProjectFilesForBuild();
+
+    fileSystem.file('pubspec.yaml')
+      ..createSync()
+      ..writeAsStringSync('version: 1.2.3+4');
+
+    processManager = FakeProcessManager.list(<FakeCommand>[
+      cmakeGenerationCommand(),
+      buildCommand('Release'),
+    ]);
+
+    await createTestCommandRunner(command).run(
+      const <String>[
+        'windows',
+        '--no-pub',
+      ]
+    );
+
+    final File cmakeConfig = fileSystem.currentDirectory
+      .childDirectory('windows')
+      .childDirectory('flutter')
+      .childDirectory('ephemeral')
+      .childFile('generated_config.cmake');
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 4 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Platform: () => windowsPlatform,
+    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+  });
+
+  testUsingContext('Windows build uses build-name and build-number', () async {
+    final FakeVisualStudio fakeVisualStudio = FakeVisualStudio();
+    final BuildWindowsCommand command = BuildWindowsCommand()
+      ..visualStudioOverride = fakeVisualStudio;
+    setUpMockProjectFilesForBuild();
+
+    processManager = FakeProcessManager.list(<FakeCommand>[
+      cmakeGenerationCommand(),
+      buildCommand('Release'),
+    ]);
+
+    await createTestCommandRunner(command).run(
+      const <String>[
+        'windows',
+        '--no-pub',
+        '--build-name=1.2.3',
+        '--build-number=4',
+      ]
+    );
+
+    final File cmakeConfig = fileSystem.currentDirectory
+      .childDirectory('windows')
+      .childDirectory('flutter')
+      .childDirectory('ephemeral')
+      .childFile('generated_config.cmake');
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 4 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Platform: () => windowsPlatform,
+    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+  });
+
+  testUsingContext('Windows build build-name overrides pubspec', () async {
+    final FakeVisualStudio fakeVisualStudio = FakeVisualStudio();
+    final BuildWindowsCommand command = BuildWindowsCommand()
+      ..visualStudioOverride = fakeVisualStudio;
+    setUpMockProjectFilesForBuild();
+
+    fileSystem.file('pubspec.yaml')
+      ..createSync()
+      ..writeAsStringSync('version: 9.9.9+9');
+
+    processManager = FakeProcessManager.list(<FakeCommand>[
+      cmakeGenerationCommand(),
+      buildCommand('Release'),
+    ]);
+
+    await createTestCommandRunner(command).run(
+      const <String>[
+        'windows',
+        '--no-pub',
+        '--build-name=1.2.3',
+      ]
+    );
+
+    final File cmakeConfig = fileSystem.currentDirectory
+      .childDirectory('windows')
+      .childDirectory('flutter')
+      .childDirectory('ephemeral')
+      .childFile('generated_config.cmake');
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Platform: () => windowsPlatform,
+    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+  });
+
+  testUsingContext('Windows build build-number overrides pubspec', () async {
+    final FakeVisualStudio fakeVisualStudio = FakeVisualStudio();
+    final BuildWindowsCommand command = BuildWindowsCommand()
+      ..visualStudioOverride = fakeVisualStudio;
+    setUpMockProjectFilesForBuild();
+
+    fileSystem.file('pubspec.yaml')
+      ..createSync()
+      ..writeAsStringSync('version: 1.2.3+9');
+
+    processManager = FakeProcessManager.list(<FakeCommand>[
+      cmakeGenerationCommand(),
+      buildCommand('Release'),
+    ]);
+
+    await createTestCommandRunner(command).run(
+      const <String>[
+        'windows',
+        '--no-pub',
+        '--build-number=4',
+      ]
+    );
+
+    final File cmakeConfig = fileSystem.currentDirectory
+      .childDirectory('windows')
+      .childDirectory('flutter')
+      .childDirectory('ephemeral')
+      .childFile('generated_config.cmake');
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 4 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Platform: () => windowsPlatform,
+    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+  });
+
+  testUsingContext('Windows build build-name and build-number override pubspec', () async {
+    final FakeVisualStudio fakeVisualStudio = FakeVisualStudio();
+    final BuildWindowsCommand command = BuildWindowsCommand()
+      ..visualStudioOverride = fakeVisualStudio;
+    setUpMockProjectFilesForBuild();
+
+    fileSystem.file('pubspec.yaml')
+      ..createSync()
+      ..writeAsStringSync('version: 9.9.9+9');
+
+    processManager = FakeProcessManager.list(<FakeCommand>[
+      cmakeGenerationCommand(),
+      buildCommand('Release'),
+    ]);
+
+    await createTestCommandRunner(command).run(
+      const <String>[
+        'windows',
+        '--no-pub',
+        '--build-name=1.2.3',
+        '--build-number=4',
+      ]
+    );
+
+    final File cmakeConfig = fileSystem.currentDirectory
+      .childDirectory('windows')
+      .childDirectory('flutter')
+      .childDirectory('ephemeral')
+      .childFile('generated_config.cmake');
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 4 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Platform: () => windowsPlatform,
+    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+  });
+
+  testUsingContext('Windows build warns on non-numeric build-number', () async {
+    final FakeVisualStudio fakeVisualStudio = FakeVisualStudio();
+    final BuildWindowsCommand command = BuildWindowsCommand()
+      ..visualStudioOverride = fakeVisualStudio;
+    setUpMockProjectFilesForBuild();
+
+    processManager = FakeProcessManager.list(<FakeCommand>[
+      cmakeGenerationCommand(),
+      buildCommand('Release'),
+    ]);
+
+    await createTestCommandRunner(command).run(
+      const <String>[
+        'windows',
+        '--no-pub',
+        '--build-name=1.2.3',
+        '--build-number=hello',
+      ]
+    );
+
+    final File cmakeConfig = fileSystem.currentDirectory
+      .childDirectory('windows')
+      .childDirectory('flutter')
+      .childDirectory('ephemeral')
+      .childFile('generated_config.cmake');
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+hello" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+
+    expect(testLogger.warningText, contains(
+      'Warning: build identifier hello in version 1.2.3+hello is not numeric and '
+      'cannot be converted into a Windows build version number. Defaulting to 0.\n'
+      'This may cause issues with Windows installers.'
+    ));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Platform: () => windowsPlatform,
+    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+  });
+
+  testUsingContext('Windows build warns on complex build-number', () async {
+    final FakeVisualStudio fakeVisualStudio = FakeVisualStudio();
+    final BuildWindowsCommand command = BuildWindowsCommand()
+      ..visualStudioOverride = fakeVisualStudio;
+    setUpMockProjectFilesForBuild();
+
+    processManager = FakeProcessManager.list(<FakeCommand>[
+      cmakeGenerationCommand(),
+      buildCommand('Release'),
+    ]);
+
+    await createTestCommandRunner(command).run(
+      const <String>[
+        'windows',
+        '--no-pub',
+        '--build-name=1.2.3',
+        '--build-number=4.5',
+      ]
+    );
+
+    final File cmakeConfig = fileSystem.currentDirectory
+      .childDirectory('windows')
+      .childDirectory('flutter')
+      .childDirectory('ephemeral')
+      .childFile('generated_config.cmake');
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4.5" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+
+    expect(testLogger.warningText, contains(
+      'Warning: build identifier 4.5 in version 1.2.3+4.5 is not numeric and '
+      'cannot be converted into a Windows build version number. Defaulting to 0.\n'
+      'This may cause issues with Windows installers.'
+    ));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Platform: () => windowsPlatform,
+    FeatureFlags: () => TestFeatureFlags(isWindowsEnabled: true),
+  });
+
   testUsingContext('hidden when not enabled on Windows host', () {
     expect(BuildWindowsCommand().hidden, true);
   }, overrides: <Type, Generator>{
diff --git a/packages/flutter_tools/test/general.shard/cmake_test.dart b/packages/flutter_tools/test/general.shard/cmake_test.dart
index 3fe01ca..091251e 100644
--- a/packages/flutter_tools/test/general.shard/cmake_test.dart
+++ b/packages/flutter_tools/test/general.shard/cmake_test.dart
@@ -5,6 +5,8 @@
 import 'package:file/memory.dart';
 import 'package:file_testing/file_testing.dart';
 import 'package:flutter_tools/src/base/file_system.dart';
+import 'package:flutter_tools/src/base/logger.dart';
+import 'package:flutter_tools/src/build_info.dart';
 import 'package:flutter_tools/src/cmake.dart';
 import 'package:flutter_tools/src/project.dart';
 
@@ -17,10 +19,12 @@
 void main() {
   late FileSystem fileSystem;
   late ProcessManager processManager;
+  late BufferLogger logger;
 
   setUp(() {
     processManager = FakeProcessManager.any();
     fileSystem = MemoryFileSystem.test();
+    logger = BufferLogger.test();
   });
 
   testUsingContext('parses executable name from cmake file', () async {
@@ -54,11 +58,13 @@
   testUsingContext('generates config', () async {
     final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
     final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
     final Map<String, String> environment = <String, String>{};
 
     writeGeneratedCmakeConfig(
       _kTestFlutterRoot,
       cmakeProject,
+      buildInfo,
       environment,
     );
 
@@ -73,6 +79,12 @@
       r'file(TO_CMAKE_PATH "/flutter" FLUTTER_ROOT)',
       r'file(TO_CMAKE_PATH "/" PROJECT_DIR)',
 
+      r'set(FLUTTER_VERSION "1.0.0" PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_PATCH 0 PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+
       r'# Environment variables to pass to tool_backend.sh',
       r'list(APPEND FLUTTER_TOOL_ENVIRONMENT',
       r'  "FLUTTER_ROOT=/flutter"',
@@ -89,6 +101,7 @@
 
     final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
     final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
 
     final Map<String, String> environment = <String, String>{
       'TEST': r'hello\world',
@@ -97,6 +110,7 @@
     writeGeneratedCmakeConfig(
       _kTestWindowsFlutterRoot,
       cmakeProject,
+      buildInfo,
       environment,
     );
 
@@ -111,6 +125,12 @@
       r'file(TO_CMAKE_PATH "C:\\flutter" FLUTTER_ROOT)',
       r'file(TO_CMAKE_PATH "C:\\" PROJECT_DIR)',
 
+      r'set(FLUTTER_VERSION "1.0.0" PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_PATCH 0 PARENT_SCOPE)',
+      r'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+
       r'# Environment variables to pass to tool_backend.sh',
       r'list(APPEND FLUTTER_TOOL_ENVIRONMENT',
       r'  "FLUTTER_ROOT=C:\\flutter"',
@@ -122,6 +142,518 @@
     FileSystem: () => fileSystem,
     ProcessManager: () => processManager,
   });
+
+  testUsingContext('generated config uses pubspec version', () async {
+    fileSystem.file('pubspec.yaml')
+      ..createSync()
+      ..writeAsStringSync('version: 1.2.3+4');
+
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(BuildMode.release, null, treeShakeIcons: false);
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 4 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+  });
+
+  testUsingContext('generated config uses build name', () async {
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: '1.2.3',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+  });
+
+  testUsingContext('generated config uses build number', () async {
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildNumber: '4',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.0.0+4" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 0 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 4 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+  });
+
+  testUsingContext('generated config uses build name and build number', () async {
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: '1.2.3',
+      buildNumber: '4',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 4 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+  });
+
+  testUsingContext('generated config uses build name over pubspec version', () async {
+    fileSystem.file('pubspec.yaml')
+      ..createSync()
+      ..writeAsStringSync('version: 9.9.9+9');
+
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: '1.2.3',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+  });
+
+  testUsingContext('generated config uses build number over pubspec version', () async {
+    fileSystem.file('pubspec.yaml')
+      ..createSync()
+      ..writeAsStringSync('version: 1.2.3+4');
+
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildNumber: '5',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+5" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 5 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+  });
+
+  testUsingContext('generated config uses build name and build number over pubspec version', () async {
+    fileSystem.file('pubspec.yaml')
+      ..createSync()
+      ..writeAsStringSync('version: 9.9.9+9');
+
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: '1.2.3',
+      buildNumber: '4',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 4 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+  });
+
+  testUsingContext('generated config ignores invalid build name', () async {
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: 'hello.world',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.0.0" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 0 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+
+    expect(logger.warningText, contains('Warning: could not parse version hello.world, defaulting to 1.0.0.'));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Logger: () => logger,
+  });
+
+  testUsingContext('generated config ignores invalid build number', () async {
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: '1.2.3',
+      buildNumber: 'foo_bar',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.0.0" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 0 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 0 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+
+    expect(logger.warningText, contains('Warning: could not parse version 1.2.3+foo_bar, defaulting to 1.0.0.'));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Logger: () => logger,
+  });
+
+  testUsingContext('generated config handles non-numeric build number', () async {
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: '1.2.3',
+      buildNumber: 'hello',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    expect(logger.warningText, isEmpty);
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+hello" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Logger: () => logger,
+  });
+
+  testUsingContext('generated config handles complex build number', () async {
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = _FakeProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: '1.2.3',
+      buildNumber: '4.5',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    expect(logger.warningText, isEmpty);
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4.5" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Logger: () => logger,
+  });
+
+  testUsingContext('generated config warns on Windows project with non-numeric build number', () async {
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = WindowsProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: '1.2.3',
+      buildNumber: 'hello',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    expect(logger.warningText, contains(
+      'Warning: build identifier hello in version 1.2.3+hello is not numeric and '
+      'cannot be converted into a Windows build version number. Defaulting to 0.\n'
+      'This may cause issues with Windows installers.'
+    ));
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+hello" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Logger: () => logger,
+  });
+
+  testUsingContext('generated config warns on Windows project with complex build number', () async {
+    final FlutterProject project = FlutterProject.fromDirectoryTest(fileSystem.currentDirectory);
+    final CmakeBasedProject cmakeProject = WindowsProject.fromFlutter(project);
+    const BuildInfo buildInfo = BuildInfo(
+      BuildMode.release,
+      null,
+      buildName: '1.2.3',
+      buildNumber: '4.5',
+      treeShakeIcons: false,
+    );
+    final Map<String, String> environment = <String, String>{};
+
+    writeGeneratedCmakeConfig(
+      _kTestFlutterRoot,
+      cmakeProject,
+      buildInfo,
+      environment,
+    );
+
+    expect(logger.warningText, contains(
+      'Warning: build identifier 4.5 in version 1.2.3+4.5 is not numeric and '
+      'cannot be converted into a Windows build version number. Defaulting to 0.\n'
+      'This may cause issues with Windows installers.'
+    ));
+
+    final File cmakeConfig = cmakeProject.generatedCmakeConfigFile;
+
+    expect(cmakeConfig, exists);
+
+    final List<String> configLines = cmakeConfig.readAsLinesSync();
+
+    expect(configLines, containsAll(<String>[
+      'set(FLUTTER_VERSION "1.2.3+4.5" PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MAJOR 1 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_MINOR 2 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_PATCH 3 PARENT_SCOPE)',
+      'set(FLUTTER_VERSION_BUILD 0 PARENT_SCOPE)',
+    ]));
+  }, overrides: <Type, Generator>{
+    FileSystem: () => fileSystem,
+    ProcessManager: () => processManager,
+    Logger: () => logger,
+  });
 }
 
 class _FakeProject implements CmakeBasedProject {
diff --git a/packages/flutter_tools/test/integration.shard/flutter_build_windows_test.dart b/packages/flutter_tools/test/integration.shard/flutter_build_windows_test.dart
index 2db718d..8311419 100644
--- a/packages/flutter_tools/test/integration.shard/flutter_build_windows_test.dart
+++ b/packages/flutter_tools/test/integration.shard/flutter_build_windows_test.dart
@@ -70,12 +70,50 @@
       expect(releaseDir, exists);
       expect(exeFile, exists);
 
-      // Default exe has version 1.0.0
+      // Default exe has build name 1.0.0 and build number 1.
       final String fileVersion = _getFileVersion(exeFile);
       final String productVersion = _getProductVersion(exeFile);
 
-      expect(fileVersion, equals('1.0.0.0'));
-      expect(productVersion, equals('1.0.0'));
+      expect(fileVersion, equals('1.0.0.1'));
+      expect(productVersion, equals('1.0.0+1'));
+    });
+
+    testWithoutContext('flutter build windows sets build name', () {
+      processManager.runSync(<String>[
+        flutterBin,
+        ...getLocalEngineArguments(),
+        'build',
+        'windows',
+        '--no-pub',
+        '--build-name',
+        '1.2.3',
+      ], workingDirectory: projectRoot.path);
+
+      final String fileVersion = _getFileVersion(exeFile);
+      final String productVersion = _getProductVersion(exeFile);
+
+      expect(fileVersion, equals('1.2.3.0'));
+      expect(productVersion, equals('1.2.3'));
+    });
+
+    testWithoutContext('flutter build windows sets build name and build number', () {
+      processManager.runSync(<String>[
+        flutterBin,
+        ...getLocalEngineArguments(),
+        'build',
+        'windows',
+        '--no-pub',
+        '--build-name',
+        '1.2.3',
+        '--build-number',
+        '4',
+      ], workingDirectory: projectRoot.path);
+
+      final String fileVersion = _getFileVersion(exeFile);
+      final String productVersion = _getProductVersion(exeFile);
+
+      expect(fileVersion, equals('1.2.3.4'));
+      expect(productVersion, equals('1.2.3+4'));
     });
   }, skip: !io.Platform.isWindows); // [intended] Windows integration build.
 }