Improve macOS version information in telemetry (#37185)

Peter Tripp created

macOS versions are currently reported as `macOS 26.0.0`.
But this makes it impossible to differentiate amongst macOS Beta
releases which have the same version number (`X.0.0`) but are different
builds.

This PR adds build number info to `os_version` for macOS Betas and
[Rapid Security Response](https://support.apple.com/en-us/102657)
release that have identical version numbers to stable release, but have
different builds numbers. We can differentiate them because the build
numbers end with a letter.

| Version | Before | After |
| - | - | - | 
| macOS Sonoma 14.7.8 | 14.7.8 | 14.7.8 |
| macOS Sequoia 15.6.1 | 15.6.1 | 15.6.1 |
| mcOS Ventura 13.3.1 | 13.3.1 | 13.3.1 |
| macOS Ventura 13.3.1 (a) |  13.3.1 | 13.3.1 (Build 22E772610a) |
| macOS Tahoe 26.0.0 (Beta1) | 26.0.0 | 26.0.0 (Build 25A5316a) |
| macOS Tahoe 26.0.0 (Beta5) | 26.0.0 | 26.0.0 (Build 25A5349a) | 

This should cause minimal telemetry changes and only impacting a macOS
betas and a couple specific older macOS versions, but will allow
differentiation between macOS beta releases in GitHub issues.

Alternatives:
1. Leave as-is (can't differentiate between macOS beta builds)
2. Always include build number info (impacts telemetry; more consistent
going forward; differentiates non-final Release Candidates which don't
include a trailing letter)

I couldn't find a cocoa method to retrieve macOS build number, so I
switched dependencies from `cocoa` to `objc2-foundation` in the client
crate. We already depend upon this crate as a dependency of
`blade-graphics` so I matched the features of that and so workspace-hack
doesn't change.

https://github.com/zed-industries/zed/blob/1ebc69a44708f344449c0c9d47e33b414277adec/tooling/workspace-hack/Cargo.toml#L355

Release Notes:

- N/A

Change summary

Cargo.lock                     |  2 +-
Cargo.toml                     | 25 +++++++++++++++++++++++++
crates/client/Cargo.toml       |  2 +-
crates/client/src/telemetry.rs | 25 +++++++++++++------------
4 files changed, 40 insertions(+), 14 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -3070,7 +3070,6 @@ dependencies = [
  "clock",
  "cloud_api_client",
  "cloud_llm_client",
- "cocoa 0.26.0",
  "collections",
  "credentials_provider",
  "derive_more",
@@ -3083,6 +3082,7 @@ dependencies = [
  "http_client_tls",
  "httparse",
  "log",
+ "objc2-foundation",
  "parking_lot",
  "paths",
  "postage",

Cargo.toml 🔗

@@ -537,6 +537,31 @@ nbformat = {  git = "https://github.com/ConradIrwin/runtimed", rev = "7130c80421
 nix = "0.29"
 num-format = "0.4.4"
 objc = "0.2"
+objc2-foundation = { version = "0.3", default-features = false, features = [
+    "NSArray",
+    "NSAttributedString",
+    "NSBundle",
+    "NSCoder",
+    "NSData",
+    "NSDate",
+    "NSDictionary",
+    "NSEnumerator",
+    "NSError",
+    "NSGeometry",
+    "NSNotification",
+    "NSNull",
+    "NSObjCRuntime",
+    "NSObject",
+    "NSProcessInfo",
+    "NSRange",
+    "NSRunLoop",
+    "NSString",
+    "NSURL",
+    "NSUndoManager",
+    "NSValue",
+    "objc2-core-foundation",
+    "std"
+] }
 open = "5.0.0"
 ordered-float = "2.1.1"
 palette = { version = "0.7.5", default-features = false, features = ["std"] }

crates/client/Cargo.toml 🔗

@@ -75,7 +75,7 @@ util = { workspace = true, features = ["test-support"] }
 windows.workspace = true
 
 [target.'cfg(target_os = "macos")'.dependencies]
-cocoa.workspace = true
+objc2-foundation.workspace = true
 
 [target.'cfg(any(target_os = "windows", target_os = "macos"))'.dependencies]
 tokio-native-tls = "0.3"

crates/client/src/telemetry.rs 🔗

@@ -84,6 +84,10 @@ static DOTNET_PROJECT_FILES_REGEX: LazyLock<Regex> = LazyLock::new(|| {
     Regex::new(r"^(global\.json|Directory\.Build\.props|.*\.(csproj|fsproj|vbproj|sln))$").unwrap()
 });
 
+#[cfg(target_os = "macos")]
+static MACOS_VERSION_REGEX: LazyLock<Regex> =
+    LazyLock::new(|| Regex::new(r"(\s*\(Build [^)]*[0-9]\))").unwrap());
+
 pub fn os_name() -> String {
     #[cfg(target_os = "macos")]
     {
@@ -108,19 +112,16 @@ pub fn os_name() -> String {
 pub fn os_version() -> String {
     #[cfg(target_os = "macos")]
     {
-        use cocoa::base::nil;
-        use cocoa::foundation::NSProcessInfo;
-
-        unsafe {
-            let process_info = cocoa::foundation::NSProcessInfo::processInfo(nil);
-            let version = process_info.operatingSystemVersion();
-            gpui::SemanticVersion::new(
-                version.majorVersion as usize,
-                version.minorVersion as usize,
-                version.patchVersion as usize,
-            )
+        use objc2_foundation::NSProcessInfo;
+        let process_info = NSProcessInfo::processInfo();
+        let version_nsstring = unsafe { process_info.operatingSystemVersionString() };
+        // "Version 15.6.1 (Build 24G90)" -> "15.6.1 (Build 24G90)"
+        let version_string = version_nsstring.to_string().replace("Version ", "");
+        // "15.6.1 (Build 24G90)" -> "15.6.1"
+        // "26.0.0 (Build 25A5349a)" -> unchanged (Beta or Rapid Security Response; ends with letter)
+        MACOS_VERSION_REGEX
+            .replace_all(&version_string, "")
             .to_string()
-        }
     }
     #[cfg(any(target_os = "linux", target_os = "freebsd"))]
     {