windows: Implement `app_version` (#9410)

张小白 created

#### Call `app_version`:

![Screenshot 2024-03-16
011821](https://github.com/zed-industries/zed/assets/14981363/9e618e49-fee2-4e7a-b884-6b0be05a0c95)

#### `Zed.exe` info:

![Screenshot 2024-03-16
011856](https://github.com/zed-industries/zed/assets/14981363/2b17a5df-ad38-42d0-8396-53680d77101d)


Release Notes:

- N/A

Change summary

Cargo.lock                                   | 25 +++++
Cargo.toml                                   |  1 
crates/gpui/src/platform/windows/platform.rs | 97 ++++++++++++++++++++-
crates/zed/Cargo.toml                        |  3 
crates/zed/build.rs                          |  9 +
5 files changed, 129 insertions(+), 6 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -9867,6 +9867,18 @@ dependencies = [
  "serde",
 ]
 
+[[package]]
+name = "toml"
+version = "0.7.8"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "dd79e69d3b627db300ff956027cc6c3798cef26d22526befdfcd12feeb6d2257"
+dependencies = [
+ "serde",
+ "serde_spanned",
+ "toml_datetime",
+ "toml_edit 0.19.15",
+]
+
 [[package]]
 name = "toml"
 version = "0.8.10"
@@ -9895,6 +9907,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "1b5bb770da30e5cbfde35a2d7b9b8a2c4b8ef89548a7a6aeab5c9a576e3e7421"
 dependencies = [
  "indexmap 2.0.0",
+ "serde",
+ "serde_spanned",
  "toml_datetime",
  "winnow 0.5.15",
 ]
@@ -12030,6 +12044,16 @@ dependencies = [
  "windows-sys 0.48.0",
 ]
 
+[[package]]
+name = "winresource"
+version = "0.1.17"
+source = "registry+https://github.com/rust-lang/crates.io-index"
+checksum = "77e2aaaf8cfa92078c0c0375423d631f82f2f57979c2884fdd5f604a11e45329"
+dependencies = [
+ "toml 0.7.8",
+ "version_check",
+]
+
 [[package]]
 name = "winx"
 version = "0.36.3"
@@ -12584,6 +12608,7 @@ dependencies = [
  "uuid",
  "vim",
  "welcome",
+ "winresource",
  "workspace",
  "zed_actions",
 ]

Cargo.toml 🔗

@@ -356,6 +356,7 @@ features = [
     "Win32_Security",
     "Win32_Security_Credentials",
     "Win32_Storage_FileSystem",
+    "Win32_System_LibraryLoader",
     "Win32_System_Com",
     "Win32_System_Com_StructuredStorage",
     "Win32_System_DataExchange",

crates/gpui/src/platform/windows/platform.rs 🔗

@@ -30,6 +30,7 @@ use windows::{
         Graphics::Gdi::*,
         Media::*,
         Security::Credentials::*,
+        Storage::FileSystem::*,
         System::{Com::*, LibraryLoader::*, Ole::*, SystemInformation::*, Threading::*, Time::*},
         UI::{Input::KeyboardAndMouse::*, Shell::*, WindowsAndMessaging::*},
     },
@@ -520,11 +521,97 @@ impl Platform for WindowsPlatform {
     }
 
     fn app_version(&self) -> Result<SemanticVersion> {
-        Ok(SemanticVersion {
-            major: 1,
-            minor: 0,
-            patch: 0,
-        })
+        let mut file_name_buffer = vec![0u16; MAX_PATH as usize];
+        let file_name = {
+            let mut file_name_buffer_capacity = MAX_PATH as usize;
+            let mut file_name_length;
+            loop {
+                file_name_length =
+                    unsafe { GetModuleFileNameW(None, &mut file_name_buffer) } as usize;
+                if file_name_length < file_name_buffer_capacity {
+                    break;
+                }
+                // buffer too small
+                file_name_buffer_capacity *= 2;
+                file_name_buffer = vec![0u16; file_name_buffer_capacity];
+            }
+            PCWSTR::from_raw(file_name_buffer[0..(file_name_length + 1)].as_ptr())
+        };
+
+        let version_info_block = {
+            let mut version_handle = 0;
+            let version_info_size =
+                unsafe { GetFileVersionInfoSizeW(file_name, Some(&mut version_handle)) } as usize;
+            if version_info_size == 0 {
+                log::error!(
+                    "unable to get version info size: {}",
+                    std::io::Error::last_os_error()
+                );
+                return Err(anyhow!("unable to get version info size"));
+            }
+            let mut version_data = vec![0u8; version_info_size + 2];
+            unsafe {
+                GetFileVersionInfoW(
+                    file_name,
+                    version_handle,
+                    version_info_size as u32,
+                    version_data.as_mut_ptr() as _,
+                )
+            }
+            .inspect_err(|_| {
+                log::error!(
+                    "unable to retrieve version info: {}",
+                    std::io::Error::last_os_error()
+                )
+            })?;
+            version_data
+        };
+
+        let version_info_raw = {
+            let mut buffer = unsafe { std::mem::zeroed() };
+            let mut size = 0;
+            let entry = "\\".encode_utf16().chain(Some(0)).collect_vec();
+            if !unsafe {
+                VerQueryValueW(
+                    version_info_block.as_ptr() as _,
+                    PCWSTR::from_raw(entry.as_ptr()),
+                    &mut buffer,
+                    &mut size,
+                )
+            }
+            .as_bool()
+            {
+                log::error!(
+                    "unable to query version info data: {}",
+                    std::io::Error::last_os_error()
+                );
+                return Err(anyhow!("the specified resource is not valid"));
+            }
+            if size == 0 {
+                log::error!(
+                    "unable to query version info data: {}",
+                    std::io::Error::last_os_error()
+                );
+                return Err(anyhow!("no value is available for the specified name"));
+            }
+            buffer
+        };
+
+        let version_info = unsafe { &*(version_info_raw as *mut VS_FIXEDFILEINFO) };
+        // https://learn.microsoft.com/en-us/windows/win32/api/verrsrc/ns-verrsrc-vs_fixedfileinfo
+        if version_info.dwSignature == 0xFEEF04BD {
+            return Ok(SemanticVersion {
+                major: ((version_info.dwProductVersionMS >> 16) & 0xFFFF) as usize,
+                minor: (version_info.dwProductVersionMS & 0xFFFF) as usize,
+                patch: ((version_info.dwProductVersionLS >> 16) & 0xFFFF) as usize,
+            });
+        } else {
+            log::error!(
+                "no version info present: {}",
+                std::io::Error::last_os_error()
+            );
+            return Err(anyhow!("no version info present"));
+        }
     }
 
     // todo(windows)

crates/zed/Cargo.toml 🔗

@@ -89,6 +89,9 @@ welcome.workspace = true
 workspace.workspace = true
 zed_actions.workspace = true
 
+[target.'cfg(target_os = "windows")'.build-dependencies]
+winresource = "0.1"
+
 [dev-dependencies]
 call = { workspace = true, features = ["test-support"] }
 editor = { workspace = true, features = ["test-support"] }

crates/zed/build.rs 🔗

@@ -44,7 +44,8 @@ fn main() {
         }
     }
 
-    if std::env::var("CARGO_CFG_TARGET_ENV").ok() == Some("msvc".to_string()) {
+    #[cfg(target_os = "windows")]
+    {
         // todo(windows): This is to avoid stack overflow. Remove it when solved.
         println!("cargo:rustc-link-arg=/stack:{}", 8 * 1024 * 1024);
 
@@ -56,5 +57,11 @@ fn main() {
             "cargo:rustc-link-arg-bins=/MANIFESTINPUT:{}",
             manifest.canonicalize().unwrap().display()
         );
+
+        let res = winresource::WindowsResource::new();
+        if let Err(e) = res.compile() {
+            eprintln!("{}", e);
+            std::process::exit(1);
+        }
     }
 }