releases: Add build number to Nightly builds (#42990)

Piotr Osiewicz and Conrad Irwin created

- **Remove semantic_version crate and use semver instead**
- **Update upload-nightly**


Release Notes:

- N/A

---------

Co-authored-by: Conrad Irwin <conrad.irwin@gmail.com>

Change summary

Cargo.lock                                                       | 38 
Cargo.toml                                                       |  5 
crates/activity_indicator/Cargo.toml                             |  1 
crates/activity_indicator/src/activity_indicator.rs              |  4 
crates/agent_ui/Cargo.toml                                       |  1 
crates/agent_ui/src/acp/entry_view_state.rs                      |  4 
crates/agent_ui/src/acp/thread_view.rs                           |  4 
crates/auto_update/Cargo.toml                                    |  1 
crates/auto_update/src/auto_update.rs                            | 75 
crates/channel/Cargo.toml                                        |  1 
crates/channel/src/channel_store_tests.rs                        |  4 
crates/cli/build.rs                                              |  3 
crates/client/Cargo.toml                                         |  1 
crates/client/src/telemetry.rs                                   |  2 
crates/collab/Cargo.toml                                         |  1 
crates/collab/src/api/extensions.rs                              |  6 
crates/collab/src/db.rs                                          |  4 
crates/collab/src/db/queries/extensions.rs                       |  9 
crates/collab/src/rpc.rs                                         | 12 
crates/collab/src/rpc/connection_pool.rs                         | 10 
crates/collab/src/tests/remote_editing_collaboration_tests.rs    | 25 
crates/collab/src/tests/test_server.rs                           |  5 
crates/editor/Cargo.toml                                         |  1 
crates/editor/benches/editor_render.rs                           |  2 
crates/editor/src/editor_tests.rs                                |  6 
crates/editor/src/inlays/inlay_hints.rs                          |  4 
crates/eval/src/eval.rs                                          | 13 
crates/extension/Cargo.toml                                      |  2 
crates/extension/src/extension.rs                                | 11 
crates/extension/src/extension_manifest.rs                       |  4 
crates/extension_host/Cargo.toml                                 |  2 
crates/extension_host/benches/extension_compilation_benchmark.rs |  6 
crates/extension_host/src/extension_host.rs                      |  9 
crates/extension_host/src/extension_store_test.rs                |  4 
crates/extension_host/src/wasm_host.rs                           | 15 
crates/extension_host/src/wasm_host/wit.rs                       | 11 
crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs          |  4 
crates/extension_host/src/wasm_host/wit/since_v0_0_4.rs          |  4 
crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs          |  4 
crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs          |  4 
crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs          |  4 
crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs          |  4 
crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs          |  4 
crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs          |  4 
crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs          |  6 
crates/extensions_ui/Cargo.toml                                  |  2 
crates/extensions_ui/src/extension_version_selector.rs           |  6 
crates/gpui/Cargo.toml                                           |  2 
crates/gpui/src/platform.rs                                      |  1 
crates/gpui/src/platform/mac/platform.rs                         | 20 
crates/language_models/Cargo.toml                                |  1 
crates/language_models/src/provider/cloud.rs                     |  9 
crates/language_tools/Cargo.toml                                 |  1 
crates/language_tools/src/lsp_log_view_tests.rs                  |  4 
crates/lsp/Cargo.toml                                            |  1 
crates/lsp/src/lsp.rs                                            |  4 
crates/project/src/project_tests.rs                              |  4 
crates/project_symbols/Cargo.toml                                |  1 
crates/project_symbols/src/project_symbols.rs                    |  4 
crates/recent_projects/Cargo.toml                                |  1 
crates/recent_projects/src/remote_connections.rs                 | 11 
crates/release_channel/Cargo.toml                                |  1 
crates/release_channel/src/lib.rs                                | 31 
crates/remote/Cargo.toml                                         |  1 
crates/remote/src/remote_client.rs                               | 14 
crates/remote/src/transport/ssh.rs                               | 37 
crates/remote/src/transport/wsl.rs                               | 23 
crates/remote_server/Cargo.toml                                  |  1 
crates/remote_server/build.rs                                    |  3 
crates/remote_server/src/remote_editing_tests.rs                 |  8 
crates/remote_server/src/remote_server.rs                        | 12 
crates/remote_server/src/unix.rs                                 | 27 
crates/semantic_version/Cargo.toml                               | 17 
crates/semantic_version/LICENSE-APACHE                           |  1 
crates/semantic_version/src/semantic_version.rs                  | 99 --
crates/system_specs/Cargo.toml                                   |  1 
crates/system_specs/src/system_specs.rs                          |  7 
crates/telemetry_events/Cargo.toml                               |  2 
crates/telemetry_events/src/telemetry_events.rs                  |  4 
crates/vim/Cargo.toml                                            |  1 
crates/vim/src/test/vim_test_context.rs                          |  5 
crates/zed/Cargo.toml                                            |  1 
crates/zed/build.rs                                              |  4 
crates/zed/src/main.rs                                           |  3 
crates/zed/src/zed.rs                                            | 22 
crates/zeta/Cargo.toml                                           |  1 
crates/zeta/src/zeta.rs                                          | 15 
crates/zeta2/Cargo.toml                                          |  1 
crates/zeta2/src/zeta2.rs                                        | 15 
crates/zeta_cli/src/headless.rs                                  | 12 
script/upload-nightly                                            |  6 
91 files changed, 372 insertions(+), 419 deletions(-)

Detailed changes

Cargo.lock 🔗

@@ -103,6 +103,7 @@ dependencies = [
  "project",
  "proto",
  "release_channel",
+ "semver",
  "smallvec",
  "ui",
  "util",
@@ -373,6 +374,7 @@ dependencies = [
  "rules_library",
  "schemars",
  "search",
+ "semver",
  "serde",
  "serde_json",
  "serde_json_lenient",
@@ -1341,6 +1343,7 @@ dependencies = [
  "parking_lot",
  "paths",
  "release_channel",
+ "semver",
  "serde",
  "serde_json",
  "settings",
@@ -2926,6 +2929,7 @@ dependencies = [
  "postage",
  "release_channel",
  "rpc",
+ "semver",
  "settings",
  "text",
  "time",
@@ -3124,6 +3128,7 @@ dependencies = [
  "release_channel",
  "rpc",
  "rustls-pki-types",
+ "semver",
  "serde",
  "serde_json",
  "serde_urlencoded",
@@ -3408,7 +3413,6 @@ dependencies = [
  "scrypt",
  "sea-orm",
  "sea-orm-macros",
- "semantic_version",
  "semver",
  "serde",
  "serde_json",
@@ -5388,6 +5392,7 @@ dependencies = [
  "rope",
  "rpc",
  "schemars",
+ "semver",
  "serde",
  "serde_json",
  "settings",
@@ -5850,7 +5855,7 @@ dependencies = [
  "parking_lot",
  "pretty_assertions",
  "proto",
- "semantic_version",
+ "semver",
  "serde",
  "serde_json",
  "task",
@@ -5916,7 +5921,7 @@ dependencies = [
  "release_channel",
  "remote",
  "reqwest_client",
- "semantic_version",
+ "semver",
  "serde",
  "serde_json",
  "serde_json_lenient",
@@ -5955,7 +5960,7 @@ dependencies = [
  "picker",
  "project",
  "release_channel",
- "semantic_version",
+ "semver",
  "serde",
  "settings",
  "smallvec",
@@ -7327,7 +7332,7 @@ dependencies = [
  "resvg",
  "schemars",
  "seahash",
- "semantic_version",
+ "semver",
  "serde",
  "serde_json",
  "slotmap",
@@ -8907,6 +8912,7 @@ dependencies = [
  "project",
  "release_channel",
  "schemars",
+ "semver",
  "serde",
  "serde_json",
  "settings",
@@ -8972,6 +8978,7 @@ dependencies = [
  "project",
  "proto",
  "release_channel",
+ "semver",
  "serde_json",
  "settings",
  "theme",
@@ -9487,6 +9494,7 @@ dependencies = [
  "postage",
  "release_channel",
  "schemars",
+ "semver",
  "serde",
  "serde_json",
  "smol",
@@ -13126,6 +13134,7 @@ dependencies = [
  "picker",
  "project",
  "release_channel",
+ "semver",
  "serde_json",
  "settings",
  "theme",
@@ -13775,6 +13784,7 @@ dependencies = [
  "project",
  "release_channel",
  "remote",
+ "semver",
  "serde",
  "serde_json",
  "settings",
@@ -13945,6 +13955,7 @@ name = "release_channel"
 version = "0.1.0"
 dependencies = [
  "gpui",
+ "semver",
 ]
 
 [[package]]
@@ -13965,6 +13976,7 @@ dependencies = [
  "release_channel",
  "rpc",
  "schemars",
+ "semver",
  "serde",
  "serde_json",
  "settings",
@@ -14029,6 +14041,7 @@ dependencies = [
  "reqwest_client",
  "rpc",
  "rust-embed",
+ "semver",
  "serde",
  "serde_json",
  "settings",
@@ -15150,14 +15163,6 @@ version = "1.2.0"
 source = "registry+https://github.com/rust-lang/crates.io-index"
 checksum = "0f7d95a54511e0c7be3f51e8867aa8cf35148d7b9445d44de2f943e2b206e749"
 
-[[package]]
-name = "semantic_version"
-version = "0.1.0"
-dependencies = [
- "anyhow",
- "serde",
-]
-
 [[package]]
 name = "semver"
 version = "1.0.27"
@@ -16882,6 +16887,7 @@ dependencies = [
  "human_bytes",
  "pciid-parser",
  "release_channel",
+ "semver",
  "serde",
  "sysinfo 0.37.2",
 ]
@@ -17029,7 +17035,7 @@ dependencies = [
 name = "telemetry_events"
 version = "0.1.0"
 dependencies = [
- "semantic_version",
+ "semver",
  "serde",
  "serde_json",
 ]
@@ -18799,6 +18805,7 @@ dependencies = [
  "release_channel",
  "schemars",
  "search",
+ "semver",
  "serde",
  "serde_json",
  "settings",
@@ -21262,6 +21269,7 @@ dependencies = [
  "reqwest_client",
  "rope",
  "search",
+ "semver",
  "serde",
  "serde_json",
  "session",
@@ -21659,6 +21667,7 @@ dependencies = [
  "release_channel",
  "reqwest_client",
  "rpc",
+ "semver",
  "serde",
  "serde_json",
  "settings",
@@ -21705,6 +21714,7 @@ dependencies = [
  "pretty_assertions",
  "project",
  "release_channel",
+ "semver",
  "serde",
  "serde_json",
  "settings",

Cargo.toml 🔗

@@ -147,7 +147,6 @@ members = [
     "crates/rules_library",
     "crates/schema_generator",
     "crates/search",
-    "crates/semantic_version",
     "crates/session",
     "crates/settings",
     "crates/settings_json",
@@ -381,7 +380,6 @@ rope = { path = "crates/rope" }
 rpc = { path = "crates/rpc" }
 rules_library = { path = "crates/rules_library" }
 search = { path = "crates/search" }
-semantic_version = { path = "crates/semantic_version" }
 session = { path = "crates/session" }
 settings = { path = "crates/settings" }
 settings_json = { path = "crates/settings_json" }
@@ -629,7 +627,7 @@ rustls-platform-verifier = "0.5.0"
 # WARNING: If you change this, you must also publish a new version of zed-scap to crates.io
 scap = { git = "https://github.com/zed-industries/scap", rev = "4afea48c3b002197176fb19cd0f9b180dd36eaac", default-features = false, package = "zed-scap", version = "0.0.8-zed" }
 schemars = { version = "1.0", features = ["indexmap2"] }
-semver = "1.0"
+semver = { version = "1.0", features = ["serde"] }
 serde = { version = "1.0.221", features = ["derive", "rc"] }
 serde_derive = "1.0.221"
 serde_json = { version = "1.0.144", features = ["preserve_order", "raw_value"] }
@@ -845,7 +843,6 @@ refineable = { codegen-units = 1 }
 release_channel = { codegen-units = 1 }
 reqwest_client = { codegen-units = 1 }
 rich_text = { codegen-units = 1 }
-semantic_version = { codegen-units = 1 }
 session = { codegen-units = 1 }
 snippet = { codegen-units = 1 }
 snippets_ui = { codegen-units = 1 }

crates/activity_indicator/Cargo.toml 🔗

@@ -23,6 +23,7 @@ gpui.workspace = true
 language.workspace = true
 project.workspace = true
 proto.workspace = true
+semver.workspace = true
 smallvec.workspace = true
 ui.workspace = true
 util.workspace = true

crates/activity_indicator/src/activity_indicator.rs 🔗

@@ -925,15 +925,15 @@ impl StatusItemView for ActivityIndicator {
 
 #[cfg(test)]
 mod tests {
-    use gpui::SemanticVersion;
     use release_channel::AppCommitSha;
+    use semver::Version;
 
     use super::*;
 
     #[test]
     fn test_version_tooltip_message() {
         let message = ActivityIndicator::version_tooltip_message(&VersionCheckType::Semantic(
-            SemanticVersion::new(1, 0, 0),
+            Version::new(1, 0, 0),
         ));
 
         assert_eq!(message, "Version: 1.0.0");

crates/agent_ui/Cargo.toml 🔗

@@ -113,6 +113,7 @@ languages = { workspace = true, features = ["test-support"] }
 language_model = { workspace = true, "features" = ["test-support"] }
 pretty_assertions.workspace = true
 project = { workspace = true, features = ["test-support"] }
+semver.workspace = true
 rand.workspace = true
 tree-sitter-md.workspace = true
 unindent.workspace = true

crates/agent_ui/src/acp/entry_view_state.rs 🔗

@@ -405,7 +405,7 @@ mod tests {
     use buffer_diff::{DiffHunkStatus, DiffHunkStatusKind};
     use editor::RowInfo;
     use fs::FakeFs;
-    use gpui::{AppContext as _, SemanticVersion, TestAppContext};
+    use gpui::{AppContext as _, TestAppContext};
 
     use crate::acp::entry_view_state::EntryViewState;
     use multi_buffer::MultiBufferRow;
@@ -539,7 +539,7 @@ mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             theme::init(theme::LoadThemes::JustBase, cx);
-            release_channel::init(SemanticVersion::default(), cx);
+            release_channel::init(semver::Version::new(0, 0, 0), cx);
         });
     }
 }

crates/agent_ui/src/acp/thread_view.rs 🔗

@@ -6086,7 +6086,7 @@ pub(crate) mod tests {
     use assistant_text_thread::TextThreadStore;
     use editor::MultiBufferOffset;
     use fs::FakeFs;
-    use gpui::{EventEmitter, SemanticVersion, TestAppContext, VisualTestContext};
+    use gpui::{EventEmitter, TestAppContext, VisualTestContext};
     use project::Project;
     use serde_json::json;
     use settings::SettingsStore;
@@ -6603,7 +6603,7 @@ pub(crate) mod tests {
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             theme::init(theme::LoadThemes::JustBase, cx);
-            release_channel::init(SemanticVersion::default(), cx);
+            release_channel::init(semver::Version::new(0, 0, 0), cx);
             prompt_store::init(cx)
         });
     }

crates/auto_update/Cargo.toml 🔗

@@ -21,6 +21,7 @@ http_client.workspace = true
 log.workspace = true
 paths.workspace = true
 release_channel.workspace = true
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true

crates/auto_update/src/auto_update.rs 🔗

@@ -2,12 +2,13 @@ use anyhow::{Context as _, Result};
 use client::Client;
 use db::kvp::KEY_VALUE_STORE;
 use gpui::{
-    App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, Global, SemanticVersion,
-    Task, Window, actions,
+    App, AppContext as _, AsyncApp, BackgroundExecutor, Context, Entity, Global, Task, Window,
+    actions,
 };
 use http_client::{HttpClient, HttpClientWithUrl};
 use paths::remote_servers_dir;
 use release_channel::{AppCommitSha, ReleaseChannel};
+use semver::Version;
 use serde::{Deserialize, Serialize};
 use settings::{RegisterSetting, Settings, SettingsStore};
 use smol::fs::File;
@@ -44,7 +45,7 @@ actions!(
 #[derive(Clone, Debug, PartialEq, Eq)]
 pub enum VersionCheckType {
     Sha(AppCommitSha),
-    Semantic(SemanticVersion),
+    Semantic(Version),
 }
 
 #[derive(Serialize, Debug)]
@@ -100,7 +101,7 @@ impl AutoUpdateStatus {
 
 pub struct AutoUpdater {
     status: AutoUpdateStatus,
-    current_version: SemanticVersion,
+    current_version: Version,
     client: Arc<Client>,
     pending_poll: Option<Task<Option<()>>>,
     quit_subscription: Option<gpui::Subscription>,
@@ -256,7 +257,7 @@ pub fn view_release_notes(_: &ViewReleaseNotes, cx: &mut App) -> Option<()> {
     match release_channel {
         ReleaseChannel::Stable | ReleaseChannel::Preview => {
             let auto_updater = auto_updater.read(cx);
-            let current_version = auto_updater.current_version;
+            let current_version = auto_updater.current_version.clone();
             let release_channel = release_channel.dev_name();
             let path = format!("/releases/{release_channel}/{current_version}");
             let url = &auto_updater.client.http_client().build_url(&path);
@@ -322,7 +323,7 @@ impl AutoUpdater {
         cx.default_global::<GlobalAutoUpdate>().0.clone()
     }
 
-    fn new(current_version: SemanticVersion, client: Arc<Client>, cx: &mut Context<Self>) -> Self {
+    fn new(current_version: Version, client: Arc<Client>, cx: &mut Context<Self>) -> Self {
         // On windows, executable files cannot be overwritten while they are
         // running, so we must wait to overwrite the application until quitting
         // or restarting. When quitting the app, we spawn the auto update helper
@@ -400,8 +401,8 @@ impl AutoUpdater {
         }));
     }
 
-    pub fn current_version(&self) -> SemanticVersion {
-        self.current_version
+    pub fn current_version(&self) -> Version {
+        self.current_version.clone()
     }
 
     pub fn status(&self) -> AutoUpdateStatus {
@@ -422,7 +423,7 @@ impl AutoUpdater {
     // Ok(None).
     pub async fn download_remote_server_release(
         release_channel: ReleaseChannel,
-        version: Option<SemanticVersion>,
+        version: Option<Version>,
         os: &str,
         arch: &str,
         set_status: impl Fn(&str, &mut AsyncApp) + Send + 'static,
@@ -469,7 +470,7 @@ impl AutoUpdater {
 
     pub async fn get_remote_server_release_url(
         channel: ReleaseChannel,
-        version: Option<SemanticVersion>,
+        version: Option<Version>,
         os: &str,
         arch: &str,
         cx: &mut AsyncApp,
@@ -491,7 +492,7 @@ impl AutoUpdater {
     async fn get_release_asset(
         this: &Entity<Self>,
         release_channel: ReleaseChannel,
-        version: Option<SemanticVersion>,
+        version: Option<Version>,
         asset: &str,
         os: &str,
         arch: &str,
@@ -554,7 +555,7 @@ impl AutoUpdater {
             this.read_with(cx, |this, cx| {
                 (
                     this.client.http_client(),
-                    this.current_version,
+                    this.current_version.clone(),
                     this.status.clone(),
                     ReleaseChannel::try_global(cx).unwrap_or(ReleaseChannel::Stable),
                 )
@@ -627,11 +628,11 @@ impl AutoUpdater {
     fn check_if_fetched_version_is_newer(
         release_channel: ReleaseChannel,
         app_commit_sha: Result<Option<String>>,
-        installed_version: SemanticVersion,
+        installed_version: Version,
         fetched_version: String,
         status: AutoUpdateStatus,
     ) -> Result<Option<VersionCheckType>> {
-        let parsed_fetched_version = fetched_version.parse::<SemanticVersion>();
+        let parsed_fetched_version = fetched_version.parse::<Version>();
 
         if let AutoUpdateStatus::Updated { version, .. } = status {
             match version {
@@ -708,8 +709,8 @@ impl AutoUpdater {
     }
 
     fn check_if_fetched_version_is_newer_non_nightly(
-        installed_version: SemanticVersion,
-        fetched_version: SemanticVersion,
+        installed_version: Version,
+        fetched_version: Version,
     ) -> Result<Option<VersionCheckType>> {
         let should_download = fetched_version > installed_version;
         let newer_version = should_download.then(|| VersionCheckType::Semantic(fetched_version));
@@ -1020,7 +1021,7 @@ mod tests {
         cx.update(|cx| {
             settings::init(cx);
 
-            let current_version = SemanticVersion::new(0, 100, 0);
+            let current_version = semver::Version::new(0, 100, 0);
             release_channel::init_test(current_version, ReleaseChannel::Stable, cx);
 
             let clock = Arc::new(FakeSystemClock::new());
@@ -1059,7 +1060,7 @@ mod tests {
 
         auto_updater.read_with(cx, |updater, _| {
             assert_eq!(updater.status(), AutoUpdateStatus::Idle);
-            assert_eq!(updater.current_version(), SemanticVersion::new(0, 100, 0));
+            assert_eq!(updater.current_version(), semver::Version::new(0, 100, 0));
         });
 
         release_available.store(true, atomic::Ordering::SeqCst);
@@ -1078,7 +1079,7 @@ mod tests {
         assert_eq!(
             status,
             AutoUpdateStatus::Downloading {
-                version: VersionCheckType::Semantic(SemanticVersion::new(0, 100, 1))
+                version: VersionCheckType::Semantic(semver::Version::new(0, 100, 1))
             }
         );
 
@@ -1108,7 +1109,7 @@ mod tests {
         assert_eq!(
             status,
             AutoUpdateStatus::Updated {
-                version: VersionCheckType::Semantic(SemanticVersion::new(0, 100, 1))
+                version: VersionCheckType::Semantic(semver::Version::new(0, 100, 1))
             }
         );
         let will_restart = cx.expect_restart();
@@ -1122,9 +1123,9 @@ mod tests {
     fn test_stable_does_not_update_when_fetched_version_is_not_higher() {
         let release_channel = ReleaseChannel::Stable;
         let app_commit_sha = Ok(Some("a".to_string()));
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Idle;
-        let fetched_version = SemanticVersion::new(1, 0, 0);
+        let fetched_version = semver::Version::new(1, 0, 0);
 
         let newer_version = AutoUpdater::check_if_fetched_version_is_newer(
             release_channel,
@@ -1141,9 +1142,9 @@ mod tests {
     fn test_stable_does_update_when_fetched_version_is_higher() {
         let release_channel = ReleaseChannel::Stable;
         let app_commit_sha = Ok(Some("a".to_string()));
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Idle;
-        let fetched_version = SemanticVersion::new(1, 0, 1);
+        let fetched_version = semver::Version::new(1, 0, 1);
 
         let newer_version = AutoUpdater::check_if_fetched_version_is_newer(
             release_channel,
@@ -1163,11 +1164,11 @@ mod tests {
     fn test_stable_does_not_update_when_fetched_version_is_not_higher_than_cached() {
         let release_channel = ReleaseChannel::Stable;
         let app_commit_sha = Ok(Some("a".to_string()));
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
-            version: VersionCheckType::Semantic(SemanticVersion::new(1, 0, 1)),
+            version: VersionCheckType::Semantic(semver::Version::new(1, 0, 1)),
         };
-        let fetched_version = SemanticVersion::new(1, 0, 1);
+        let fetched_version = semver::Version::new(1, 0, 1);
 
         let newer_version = AutoUpdater::check_if_fetched_version_is_newer(
             release_channel,
@@ -1184,11 +1185,11 @@ mod tests {
     fn test_stable_does_update_when_fetched_version_is_higher_than_cached() {
         let release_channel = ReleaseChannel::Stable;
         let app_commit_sha = Ok(Some("a".to_string()));
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
-            version: VersionCheckType::Semantic(SemanticVersion::new(1, 0, 1)),
+            version: VersionCheckType::Semantic(semver::Version::new(1, 0, 1)),
         };
-        let fetched_version = SemanticVersion::new(1, 0, 2);
+        let fetched_version = semver::Version::new(1, 0, 2);
 
         let newer_version = AutoUpdater::check_if_fetched_version_is_newer(
             release_channel,
@@ -1208,7 +1209,7 @@ mod tests {
     fn test_nightly_does_not_update_when_fetched_sha_is_same() {
         let release_channel = ReleaseChannel::Nightly;
         let app_commit_sha = Ok(Some("a".to_string()));
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Idle;
         let fetched_sha = "a".to_string();
 
@@ -1227,7 +1228,7 @@ mod tests {
     fn test_nightly_does_update_when_fetched_sha_is_not_same() {
         let release_channel = ReleaseChannel::Nightly;
         let app_commit_sha = Ok(Some("a".to_string()));
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Idle;
         let fetched_sha = "b".to_string();
 
@@ -1249,7 +1250,7 @@ mod tests {
     fn test_nightly_does_not_update_when_fetched_sha_is_same_as_cached() {
         let release_channel = ReleaseChannel::Nightly;
         let app_commit_sha = Ok(Some("a".to_string()));
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
             version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
         };
@@ -1270,7 +1271,7 @@ mod tests {
     fn test_nightly_does_update_when_fetched_sha_is_not_same_as_cached() {
         let release_channel = ReleaseChannel::Nightly;
         let app_commit_sha = Ok(Some("a".to_string()));
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
             version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
         };
@@ -1294,7 +1295,7 @@ mod tests {
     fn test_nightly_does_update_when_installed_versions_sha_cannot_be_retrieved() {
         let release_channel = ReleaseChannel::Nightly;
         let app_commit_sha = Ok(None);
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Idle;
         let fetched_sha = "a".to_string();
 
@@ -1317,7 +1318,7 @@ mod tests {
      {
         let release_channel = ReleaseChannel::Nightly;
         let app_commit_sha = Ok(None);
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
             version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
         };
@@ -1339,7 +1340,7 @@ mod tests {
      {
         let release_channel = ReleaseChannel::Nightly;
         let app_commit_sha = Ok(None);
-        let installed_version = SemanticVersion::new(1, 0, 0);
+        let installed_version = semver::Version::new(1, 0, 0);
         let status = AutoUpdateStatus::Updated {
             version: VersionCheckType::Sha(AppCommitSha::new("b".to_string())),
         };

crates/channel/Cargo.toml 🔗

@@ -37,6 +37,7 @@ collections = { workspace = true, features = ["test-support"] }
 gpui = { workspace = true, features = ["test-support"] }
 rpc = { workspace = true, features = ["test-support"] }
 client = { workspace = true, features = ["test-support"] }
+semver.workspace = true
 settings = { workspace = true, features = ["test-support"] }
 util = { workspace = true, features = ["test-support"] }
 http_client = { workspace = true, features = ["test-support"] }

crates/channel/src/channel_store_tests.rs 🔗

@@ -1,7 +1,7 @@
 use super::*;
 use client::{Client, UserStore};
 use clock::FakeSystemClock;
-use gpui::{App, AppContext as _, Entity, SemanticVersion};
+use gpui::{App, AppContext as _, Entity};
 use http_client::FakeHttpClient;
 use rpc::proto::{self};
 use settings::SettingsStore;
@@ -236,7 +236,7 @@ fn test_dangling_channel_paths(cx: &mut App) {
 fn init_test(cx: &mut App) -> Entity<ChannelStore> {
     let settings_store = SettingsStore::test(cx);
     cx.set_global(settings_store);
-    release_channel::init(SemanticVersion::default(), cx);
+    release_channel::init(semver::Version::new(0, 0, 0), cx);
 
     let clock = Arc::new(FakeSystemClock::new());
     let http = FakeHttpClient::with_404_response();

crates/cli/build.rs 🔗

@@ -23,4 +23,7 @@ fn main() {
 
         println!("cargo:rustc-env=ZED_COMMIT_SHA={git_sha}");
     }
+    if let Some(build_identifier) = option_env!("GITHUB_RUN_NUMBER") {
+        println!("cargo:rustc-env=ZED_BUILD_ID={build_identifier}");
+    }
 }

crates/client/Cargo.toml 🔗

@@ -70,6 +70,7 @@ settings = { workspace = true, features = ["test-support"] }
 util = { workspace = true, features = ["test-support"] }
 
 [target.'cfg(target_os = "windows")'.dependencies]
+semver.workspace = true
 windows.workspace = true
 
 [target.'cfg(target_os = "macos")'.dependencies]

crates/client/src/telemetry.rs 🔗

@@ -158,7 +158,7 @@ pub fn os_version() -> String {
         let mut info = unsafe { std::mem::zeroed() };
         let status = unsafe { windows::Wdk::System::SystemServices::RtlGetVersion(&mut info) };
         if status.is_ok() {
-            gpui::SemanticVersion::new(
+            semver::Version::new(
                 info.dwMajorVersion as _,
                 info.dwMinorVersion as _,
                 info.dwBuildNumber as _,

crates/collab/Cargo.toml 🔗

@@ -50,7 +50,6 @@ scrypt = "0.11"
 # sea-orm and sea-orm-macros versions must match exactly.
 sea-orm = { version = "=1.1.10", features = ["sqlx-postgres", "postgres-array", "runtime-tokio-rustls", "with-uuid"] }
 sea-orm-macros = "=1.1.10"
-semantic_version.workspace = true
 semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true

crates/collab/src/api/extensions.rs 🔗

@@ -11,7 +11,7 @@ use axum::{
 };
 use collections::{BTreeSet, HashMap};
 use rpc::{ExtensionApiManifest, ExtensionProvides, GetExtensionsResponse};
-use semantic_version::SemanticVersion;
+use semver::Version as SemanticVersion;
 use serde::Deserialize;
 use std::str::FromStr;
 use std::{sync::Arc, time::Duration};
@@ -108,8 +108,8 @@ struct GetExtensionUpdatesParams {
     ids: String,
     min_schema_version: i32,
     max_schema_version: i32,
-    min_wasm_api_version: SemanticVersion,
-    max_wasm_api_version: SemanticVersion,
+    min_wasm_api_version: semver::Version,
+    max_wasm_api_version: semver::Version,
 }
 
 async fn get_extension_updates(

crates/collab/src/db.rs 🔗

@@ -22,7 +22,7 @@ use sea_orm::{
     entity::prelude::*,
     sea_query::{Alias, Expr, OnConflict},
 };
-use semantic_version::SemanticVersion;
+use semver::Version;
 use serde::{Deserialize, Serialize};
 use std::ops::RangeInclusive;
 use std::{
@@ -671,7 +671,7 @@ pub struct NewExtensionVersion {
 
 pub struct ExtensionVersionConstraints {
     pub schema_versions: RangeInclusive<i32>,
-    pub wasm_api_versions: RangeInclusive<SemanticVersion>,
+    pub wasm_api_versions: RangeInclusive<semver::Version>,
 }
 
 impl LocalSettingsKind {

crates/collab/src/db/queries/extensions.rs 🔗

@@ -69,7 +69,7 @@ impl Database {
         extensions: &[extension::Model],
         constraints: Option<&ExtensionVersionConstraints>,
         tx: &DatabaseTransaction,
-    ) -> Result<HashMap<ExtensionId, (extension_version::Model, SemanticVersion)>> {
+    ) -> Result<HashMap<ExtensionId, (extension_version::Model, Version)>> {
         let mut versions = extension_version::Entity::find()
             .filter(
                 extension_version::Column::ExtensionId
@@ -79,11 +79,10 @@ impl Database {
             .await?;
 
         let mut max_versions =
-            HashMap::<ExtensionId, (extension_version::Model, SemanticVersion)>::default();
+            HashMap::<ExtensionId, (extension_version::Model, Version)>::default();
         while let Some(version) = versions.next().await {
             let version = version?;
-            let Some(extension_version) = SemanticVersion::from_str(&version.version).log_err()
-            else {
+            let Some(extension_version) = Version::from_str(&version.version).log_err() else {
                 continue;
             };
 
@@ -102,7 +101,7 @@ impl Database {
                 }
 
                 if let Some(wasm_api_version) = version.wasm_api_version.as_ref() {
-                    if let Some(version) = SemanticVersion::from_str(wasm_api_version).log_err() {
+                    if let Some(version) = Version::from_str(wasm_api_version).log_err() {
                         if !constraints.wasm_api_versions.contains(&version) {
                             continue;
                         }

crates/collab/src/rpc.rs 🔗

@@ -50,7 +50,7 @@ use rpc::{
         RequestMessage, ShareProject, UpdateChannelBufferCollaborators,
     },
 };
-use semantic_version::SemanticVersion;
+use semver::Version;
 use serde::{Serialize, Serializer};
 use std::{
     any::TypeId,
@@ -985,14 +985,14 @@ impl Server {
 
                 {
                     let mut pool = self.connection_pool.lock();
-                    pool.add_connection(connection_id, user.id, user.admin, zed_version);
+                    pool.add_connection(connection_id, user.id, user.admin, zed_version.clone());
                     self.peer.send(
                         connection_id,
                         build_initial_contacts_update(contacts, &pool),
                     )?;
                 }
 
-                if should_auto_subscribe_to_channels(zed_version) {
+                if should_auto_subscribe_to_channels(&zed_version) {
                     subscribe_user_to_channels(user.id, session).await?;
                 }
 
@@ -1136,7 +1136,7 @@ impl Header for ProtocolVersion {
     }
 }
 
-pub struct AppVersionHeader(SemanticVersion);
+pub struct AppVersionHeader(Version);
 impl Header for AppVersionHeader {
     fn name() -> &'static HeaderName {
         static ZED_APP_VERSION: OnceLock<HeaderName> = OnceLock::new();
@@ -2834,8 +2834,8 @@ async fn remove_contact(
     Ok(())
 }
 
-fn should_auto_subscribe_to_channels(version: ZedVersion) -> bool {
-    version.0.minor() < 139
+fn should_auto_subscribe_to_channels(version: &ZedVersion) -> bool {
+    version.0.minor < 139
 }
 
 async fn subscribe_to_channels(

crates/collab/src/rpc/connection_pool.rs 🔗

@@ -2,7 +2,7 @@ use crate::db::{ChannelId, ChannelRole, UserId};
 use anyhow::{Context as _, Result};
 use collections::{BTreeMap, HashMap, HashSet};
 use rpc::ConnectionId;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use serde::Serialize;
 use std::fmt;
 use tracing::instrument;
@@ -19,8 +19,8 @@ struct ConnectedPrincipal {
     connection_ids: HashSet<ConnectionId>,
 }
 
-#[derive(Copy, Clone, Debug, Serialize, PartialOrd, PartialEq, Eq, Ord)]
-pub struct ZedVersion(pub SemanticVersion);
+#[derive(Clone, Debug, Serialize, PartialOrd, PartialEq, Eq, Ord)]
+pub struct ZedVersion(pub Version);
 
 impl fmt::Display for ZedVersion {
     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
@@ -32,13 +32,13 @@ impl ZedVersion {
     pub fn can_collaborate(&self) -> bool {
         // v0.204.1 was the first version after the auto-update bug.
         // We reject any clients older than that to hope we can persuade them to upgrade.
-        if self.0 < SemanticVersion::new(0, 204, 1) {
+        if self.0 < Version::new(0, 204, 1) {
             return false;
         }
 
         // Since we hotfixed the changes to no longer connect to Collab automatically to Preview, we also need to reject
         // versions in the range [v0.199.0, v0.199.1].
-        if self.0 >= SemanticVersion::new(0, 199, 0) && self.0 < SemanticVersion::new(0, 199, 2) {
+        if self.0 >= Version::new(0, 199, 0) && self.0 < Version::new(0, 199, 2) {
             return false;
         }
 

crates/collab/src/tests/remote_editing_collaboration_tests.rs 🔗

@@ -7,10 +7,7 @@ use debugger_ui::debugger_panel::DebugPanel;
 use extension::ExtensionHostProxy;
 use fs::{FakeFs, Fs as _, RemoveOptions};
 use futures::StreamExt as _;
-use gpui::{
-    AppContext as _, BackgroundExecutor, SemanticVersion, TestAppContext, UpdateGlobal as _,
-    VisualContext,
-};
+use gpui::{AppContext as _, BackgroundExecutor, TestAppContext, UpdateGlobal as _, VisualContext};
 use http_client::BlockedHttpClient;
 use language::{
     FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, LanguageRegistry,
@@ -43,10 +40,10 @@ async fn test_sharing_an_ssh_remote_project(
 ) {
     let executor = cx_a.executor();
     cx_a.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
     server_cx.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
     let mut server = TestServer::start(executor.clone()).await;
     let client_a = server.create_client(cx_a, "user_a").await;
@@ -211,10 +208,10 @@ async fn test_ssh_collaboration_git_branches(
     server_cx.set_name("server");
 
     cx_a.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
     server_cx.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
 
     let mut server = TestServer::start(executor.clone()).await;
@@ -396,10 +393,10 @@ async fn test_ssh_collaboration_formatting_with_prettier(
     server_cx.set_name("server");
 
     cx_a.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
     server_cx.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
 
     let mut server = TestServer::start(executor.clone()).await;
@@ -583,13 +580,13 @@ async fn test_remote_server_debugger(
     executor: BackgroundExecutor,
 ) {
     cx_a.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
         command_palette_hooks::init(cx);
         zlog::init_test();
         dap_adapters::init(cx);
     });
     server_cx.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
         dap_adapters::init(cx);
     });
     let (opts, server_ssh) = RemoteClient::fake_server(cx_a, server_cx);
@@ -691,13 +688,13 @@ async fn test_slow_adapter_startup_retries(
     executor: BackgroundExecutor,
 ) {
     cx_a.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
         command_palette_hooks::init(cx);
         zlog::init_test();
         dap_adapters::init(cx);
     });
     server_cx.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
         dap_adapters::init(cx);
     });
     let (opts, server_ssh) = RemoteClient::fake_server(cx_a, server_cx);

crates/collab/src/tests/test_server.rs 🔗

@@ -31,7 +31,6 @@ use rpc::{
     RECEIVE_TIMEOUT,
     proto::{self, ChannelRole},
 };
-use semantic_version::SemanticVersion;
 use serde_json::json;
 use session::{AppSession, Session};
 use settings::SettingsStore;
@@ -173,7 +172,7 @@ impl TestServer {
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
             theme::init(theme::LoadThemes::JustBase, cx);
-            release_channel::init(SemanticVersion::default(), cx);
+            release_channel::init(semver::Version::new(0, 0, 0), cx);
         });
 
         let clock = Arc::new(FakeSystemClock::new());
@@ -295,7 +294,7 @@ impl TestServer {
                             server_conn,
                             client_name,
                             Principal::User(user),
-                            ZedVersion(SemanticVersion::new(1, 0, 0)),
+                            ZedVersion(semver::Version::new(1, 0, 0)),
                             Some("test".to_string()),
                             None,
                             None,

crates/editor/Cargo.toml 🔗

@@ -106,6 +106,7 @@ multi_buffer = { workspace = true, features = ["test-support"] }
 project = { workspace = true, features = ["test-support"] }
 release_channel.workspace = true
 rand.workspace = true
+semver.workspace = true
 settings = { workspace = true, features = ["test-support"] }
 tempfile.workspace = true
 text = { workspace = true, features = ["test-support"] }

crates/editor/benches/editor_render.rs 🔗

@@ -123,7 +123,7 @@ pub fn benches() {
         cx.set_global(store);
         assets::Assets.load_test_fonts(cx);
         theme::init(theme::LoadThemes::JustBase, cx);
-        // release_channel::init(SemanticVersion::default(), cx);
+        // release_channel::init(semver::Version::new(0,0,0), cx);
         editor::init(cx);
     });
 

crates/editor/src/editor_tests.rs 🔗

@@ -17,8 +17,8 @@ use buffer_diff::{BufferDiff, DiffHunkSecondaryStatus, DiffHunkStatus, DiffHunkS
 use collections::HashMap;
 use futures::{StreamExt, channel::oneshot};
 use gpui::{
-    BackgroundExecutor, DismissEvent, Rgba, SemanticVersion, TestAppContext, UpdateGlobal,
-    VisualTestContext, WindowBounds, WindowOptions, div,
+    BackgroundExecutor, DismissEvent, Rgba, TestAppContext, UpdateGlobal, VisualTestContext,
+    WindowBounds, WindowOptions, div,
 };
 use indoc::indoc;
 use language::{
@@ -26303,7 +26303,7 @@ pub(crate) fn init_test(cx: &mut TestAppContext, f: fn(&mut AllLanguageSettingsC
         let store = SettingsStore::test(cx);
         cx.set_global(store);
         theme::init(theme::LoadThemes::JustBase, cx);
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
         crate::init(cx);
     });
     zlog::init_test();

crates/editor/src/inlays/inlay_hints.rs 🔗

@@ -941,7 +941,7 @@ pub mod tests {
     use crate::{ExcerptRange, scroll::Autoscroll};
     use collections::HashSet;
     use futures::{StreamExt, future};
-    use gpui::{AppContext as _, Context, SemanticVersion, TestAppContext, WindowHandle};
+    use gpui::{AppContext as _, Context, TestAppContext, WindowHandle};
     use itertools::Itertools as _;
     use language::language_settings::InlayHintKind;
     use language::{Capability, FakeLspAdapter};
@@ -4062,7 +4062,7 @@ let c = 3;"#
             let settings_store = SettingsStore::test(cx);
             cx.set_global(settings_store);
             theme::init(theme::LoadThemes::JustBase, cx);
-            release_channel::init(SemanticVersion::default(), cx);
+            release_channel::init(semver::Version::new(0, 0, 0), cx);
             crate::init(cx);
         });
 

crates/eval/src/eval.rs 🔗

@@ -25,7 +25,7 @@ use language_model::{ConfiguredModel, LanguageModel, LanguageModelRegistry, Sele
 use node_runtime::{NodeBinaryOptions, NodeRuntime};
 use project::project_settings::ProjectSettings;
 use prompt_store::PromptBuilder;
-use release_channel::AppVersion;
+use release_channel::{AppCommitSha, AppVersion};
 use reqwest_client::ReqwestClient;
 use settings::{Settings, SettingsStore};
 use std::cell::RefCell;
@@ -347,8 +347,15 @@ pub struct AgentAppState {
 }
 
 pub fn init(cx: &mut App) -> Arc<AgentAppState> {
-    let app_version = AppVersion::load(env!("ZED_PKG_VERSION"));
-    release_channel::init(app_version, cx);
+    let app_commit_sha = option_env!("ZED_COMMIT_SHA").map(|s| AppCommitSha::new(s.to_owned()));
+
+    let app_version = AppVersion::load(
+        env!("ZED_PKG_VERSION"),
+        option_env!("ZED_BUILD_ID"),
+        app_commit_sha,
+    );
+
+    release_channel::init(app_version.clone(), cx);
     gpui_tokio::init(cx);
 
     let settings_store = SettingsStore::new(cx, &settings::default_settings());

crates/extension/Cargo.toml 🔗

@@ -26,7 +26,7 @@ log.workspace = true
 lsp.workspace = true
 parking_lot.workspace = true
 proto.workspace = true
-semantic_version.workspace = true
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 task.workspace = true

crates/extension/src/extension.rs 🔗

@@ -14,7 +14,7 @@ use async_trait::async_trait;
 use fs::normalize_path;
 use gpui::{App, Task};
 use language::LanguageName;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use task::{SpawnInTerminal, ZedDebugConfig};
 use util::rel_path::RelPath;
 
@@ -170,10 +170,7 @@ pub trait Extension: Send + Sync + 'static {
     ) -> Result<DebugRequest>;
 }
 
-pub fn parse_wasm_extension_version(
-    extension_id: &str,
-    wasm_bytes: &[u8],
-) -> Result<SemanticVersion> {
+pub fn parse_wasm_extension_version(extension_id: &str, wasm_bytes: &[u8]) -> Result<Version> {
     let mut version = None;
 
     for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
@@ -200,9 +197,9 @@ pub fn parse_wasm_extension_version(
     version.with_context(|| format!("extension {extension_id} has no zed:api-version section"))
 }
 
-fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
+fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<Version> {
     if data.len() == 6 {
-        Some(SemanticVersion::new(
+        Some(Version::new(
             u16::from_be_bytes([data[0], data[1]]) as _,
             u16::from_be_bytes([data[2], data[3]]) as _,
             u16::from_be_bytes([data[4], data[5]]) as _,

crates/extension/src/extension_manifest.rs 🔗

@@ -3,7 +3,7 @@ use collections::{BTreeMap, HashMap};
 use fs::Fs;
 use language::LanguageName;
 use lsp::LanguageServerName;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use serde::{Deserialize, Serialize};
 use std::{
     ffi::OsStr,
@@ -137,7 +137,7 @@ pub fn build_debug_adapter_schema_path(
 #[derive(Clone, Default, PartialEq, Eq, Debug, Deserialize, Serialize)]
 pub struct LibManifestEntry {
     pub kind: Option<ExtensionLibraryKind>,
-    pub version: Option<SemanticVersion>,
+    pub version: Option<Version>,
 }
 
 #[derive(Clone, PartialEq, Eq, Debug, Deserialize, Serialize)]

crates/extension_host/Cargo.toml 🔗

@@ -38,7 +38,7 @@ paths.workspace = true
 project.workspace = true
 remote.workspace = true
 release_channel.workspace = true
-semantic_version.workspace = true
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 serde_json_lenient.workspace = true

crates/extension_host/benches/extension_compilation_benchmark.rs 🔗

@@ -8,7 +8,7 @@ use extension::{
 };
 use extension_host::wasm_host::WasmHost;
 use fs::RealFs;
-use gpui::{SemanticVersion, TestAppContext, TestDispatcher};
+use gpui::{TestAppContext, TestDispatcher};
 use http_client::{FakeHttpClient, Response};
 use node_runtime::NodeRuntime;
 use rand::{SeedableRng, rngs::StdRng};
@@ -54,7 +54,7 @@ fn init() -> TestAppContext {
     cx.update(|cx| {
         let store = SettingsStore::test(cx);
         cx.set_global(store);
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
 
     cx
@@ -124,7 +124,7 @@ fn manifest() -> ExtensionManifest {
         icon_themes: Vec::new(),
         lib: LibManifestEntry {
             kind: Some(ExtensionLibraryKind::Rust),
-            version: Some(SemanticVersion::new(0, 1, 0)),
+            version: Some(semver::Version::new(0, 1, 0)),
         },
         languages: Vec::new(),
         grammars: BTreeMap::default(),

crates/extension_host/src/extension_host.rs 🔗

@@ -44,7 +44,7 @@ use node_runtime::NodeRuntime;
 use project::ContextProviderWithTasks;
 use release_channel::ReleaseChannel;
 use remote::RemoteClient;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use serde::{Deserialize, Serialize};
 use settings::Settings;
 use std::ops::RangeInclusive;
@@ -98,7 +98,7 @@ pub fn is_version_compatible(
         .manifest
         .wasm_api_version
         .as_ref()
-        .and_then(|wasm_api_version| SemanticVersion::from_str(wasm_api_version).ok())
+        .and_then(|wasm_api_version| Version::from_str(wasm_api_version).ok())
         && !is_supported_wasm_api_version(release_channel, wasm_api_version)
     {
         return false;
@@ -639,9 +639,8 @@ impl ExtensionStore {
                     this.extension_index.extensions.get(&extension.id)
                 {
                     let installed_version =
-                        SemanticVersion::from_str(&installed_extension.manifest.version).ok()?;
-                    let latest_version =
-                        SemanticVersion::from_str(&extension.manifest.version).ok()?;
+                        Version::from_str(&installed_extension.manifest.version).ok()?;
+                    let latest_version = Version::from_str(&extension.manifest.version).ok()?;
 
                     if installed_version >= latest_version {
                         return None;

crates/extension_host/src/extension_store_test.rs 🔗

@@ -8,7 +8,7 @@ use collections::{BTreeMap, HashSet};
 use extension::ExtensionHostProxy;
 use fs::{FakeFs, Fs, RealFs};
 use futures::{AsyncReadExt, StreamExt, io::BufReader};
-use gpui::{AppContext as _, SemanticVersion, TestAppContext};
+use gpui::{AppContext as _, TestAppContext};
 use http_client::{FakeHttpClient, Response};
 use language::{BinaryStatus, LanguageMatcher, LanguageName, LanguageRegistry};
 use language_extension::LspAccess;
@@ -866,7 +866,7 @@ fn init_test(cx: &mut TestAppContext) {
     cx.update(|cx| {
         let store = SettingsStore::test(cx);
         cx.set_global(store);
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
         extension::init(cx);
         theme::init(theme::LoadThemes::JustBase, cx);
         gpui_tokio::init(cx);

crates/extension_host/src/wasm_host.rs 🔗

@@ -28,7 +28,7 @@ use lsp::LanguageServerName;
 use moka::sync::Cache;
 use node_runtime::NodeRuntime;
 use release_channel::ReleaseChannel;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use settings::Settings;
 use std::{
     borrow::Cow,
@@ -68,7 +68,7 @@ pub struct WasmExtension {
     pub manifest: Arc<ExtensionManifest>,
     pub work_dir: Arc<Path>,
     #[allow(unused)]
-    pub zed_api_version: SemanticVersion,
+    pub zed_api_version: Version,
     _task: Arc<Task<Result<(), gpui_tokio::JoinError>>>,
 }
 
@@ -630,7 +630,7 @@ impl WasmHost {
                 &executor,
                 &mut store,
                 this.release_channel,
-                zed_api_version,
+                zed_api_version.clone(),
                 &component,
             )
             .await?;
@@ -713,10 +713,7 @@ impl WasmHost {
     }
 }
 
-pub fn parse_wasm_extension_version(
-    extension_id: &str,
-    wasm_bytes: &[u8],
-) -> Result<SemanticVersion> {
+pub fn parse_wasm_extension_version(extension_id: &str, wasm_bytes: &[u8]) -> Result<Version> {
     let mut version = None;
 
     for part in wasmparser::Parser::new(0).parse_all(wasm_bytes) {
@@ -743,9 +740,9 @@ pub fn parse_wasm_extension_version(
     version.with_context(|| format!("extension {extension_id} has no zed:api-version section"))
 }
 
-fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<SemanticVersion> {
+fn parse_wasm_extension_version_custom_section(data: &[u8]) -> Option<Version> {
     if data.len() == 6 {
-        Some(SemanticVersion::new(
+        Some(Version::new(
             u16::from_be_bytes([data[0], data[1]]) as _,
             u16::from_be_bytes([data[2], data[3]]) as _,
             u16::from_be_bytes([data[4], data[5]]) as _,

crates/extension_host/src/wasm_host/wit.rs 🔗

@@ -19,7 +19,7 @@ use crate::wasm_host::wit::since_v0_6_0::dap::StartDebuggingRequestArgumentsRequ
 
 use super::{WasmState, wasm_engine};
 use anyhow::{Context as _, Result, anyhow};
-use semantic_version::SemanticVersion;
+use semver::Version;
 use since_v0_6_0 as latest;
 use std::{ops::RangeInclusive, path::PathBuf, sync::Arc};
 use wasmtime::{
@@ -54,16 +54,13 @@ fn wasi_view(state: &mut WasmState) -> &mut WasmState {
 }
 
 /// Returns whether the given Wasm API version is supported by the Wasm host.
-pub fn is_supported_wasm_api_version(
-    release_channel: ReleaseChannel,
-    version: SemanticVersion,
-) -> bool {
+pub fn is_supported_wasm_api_version(release_channel: ReleaseChannel, version: Version) -> bool {
     wasm_api_version_range(release_channel).contains(&version)
 }
 
 /// Returns the Wasm API version range that is supported by the Wasm host.
 #[inline(always)]
-pub fn wasm_api_version_range(release_channel: ReleaseChannel) -> RangeInclusive<SemanticVersion> {
+pub fn wasm_api_version_range(release_channel: ReleaseChannel) -> RangeInclusive<Version> {
     // Note: The release channel can be used to stage a new version of the extension API.
     let _ = release_channel;
 
@@ -114,7 +111,7 @@ impl Extension {
         executor: &BackgroundExecutor,
         store: &mut Store<WasmState>,
         release_channel: ReleaseChannel,
-        version: SemanticVersion,
+        version: Version,
         component: &Component,
     ) -> Result<Self> {
         // Note: The release channel can be used to stage a new version of the extension API.

crates/extension_host/src/wasm_host/wit/since_v0_0_1.rs 🔗

@@ -5,11 +5,11 @@ use anyhow::Result;
 use extension::{ExtensionLanguageServerProxy, WorktreeDelegate};
 use gpui::BackgroundExecutor;
 use language::BinaryStatus;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use std::sync::{Arc, OnceLock};
 use wasmtime::component::{Linker, Resource};
 
-pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 1);
+pub const MIN_VERSION: Version = Version::new(0, 0, 1);
 
 wasmtime::component::bindgen!({
     async: true,

crates/extension_host/src/wasm_host/wit/since_v0_0_4.rs 🔗

@@ -3,11 +3,11 @@ use crate::wasm_host::WasmState;
 use anyhow::Result;
 use extension::WorktreeDelegate;
 use gpui::BackgroundExecutor;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use std::sync::{Arc, OnceLock};
 use wasmtime::component::{Linker, Resource};
 
-pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 4);
+pub const MIN_VERSION: Version = Version::new(0, 0, 4);
 
 wasmtime::component::bindgen!({
     async: true,

crates/extension_host/src/wasm_host/wit/since_v0_0_6.rs 🔗

@@ -3,11 +3,11 @@ use crate::wasm_host::WasmState;
 use anyhow::Result;
 use extension::WorktreeDelegate;
 use gpui::BackgroundExecutor;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use std::sync::{Arc, OnceLock};
 use wasmtime::component::{Linker, Resource};
 
-pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 0, 6);
+pub const MIN_VERSION: Version = Version::new(0, 0, 6);
 
 wasmtime::component::bindgen!({
     async: true,

crates/extension_host/src/wasm_host/wit/since_v0_1_0.rs 🔗

@@ -11,7 +11,7 @@ use gpui::BackgroundExecutor;
 use language::LanguageName;
 use language::{BinaryStatus, language_settings::AllLanguageSettings};
 use project::project_settings::ProjectSettings;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use std::{
     path::{Path, PathBuf},
     sync::{Arc, OnceLock},
@@ -23,7 +23,7 @@ use wasmtime::component::{Linker, Resource};
 
 use super::latest;
 
-pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 1, 0);
+pub const MIN_VERSION: Version = Version::new(0, 1, 0);
 
 wasmtime::component::bindgen!({
     async: true,

crates/extension_host/src/wasm_host/wit/since_v0_2_0.rs 🔗

@@ -2,13 +2,13 @@ use crate::wasm_host::WasmState;
 use anyhow::Result;
 use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate};
 use gpui::BackgroundExecutor;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use std::sync::{Arc, OnceLock};
 use wasmtime::component::{Linker, Resource};
 
 use super::latest;
 
-pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 2, 0);
+pub const MIN_VERSION: Version = Version::new(0, 2, 0);
 
 wasmtime::component::bindgen!({
     async: true,

crates/extension_host/src/wasm_host/wit/since_v0_3_0.rs 🔗

@@ -2,13 +2,13 @@ use crate::wasm_host::WasmState;
 use anyhow::Result;
 use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate};
 use gpui::BackgroundExecutor;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use std::sync::{Arc, OnceLock};
 use wasmtime::component::{Linker, Resource};
 
 use super::latest;
 
-pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 3, 0);
+pub const MIN_VERSION: Version = Version::new(0, 3, 0);
 
 wasmtime::component::bindgen!({
     async: true,

crates/extension_host/src/wasm_host/wit/since_v0_4_0.rs 🔗

@@ -2,13 +2,13 @@ use crate::wasm_host::WasmState;
 use anyhow::Result;
 use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate};
 use gpui::BackgroundExecutor;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use std::sync::{Arc, OnceLock};
 use wasmtime::component::{Linker, Resource};
 
 use super::latest;
 
-pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 4, 0);
+pub const MIN_VERSION: Version = Version::new(0, 4, 0);
 
 wasmtime::component::bindgen!({
     async: true,

crates/extension_host/src/wasm_host/wit/since_v0_5_0.rs 🔗

@@ -2,13 +2,13 @@ use crate::wasm_host::WasmState;
 use anyhow::Result;
 use extension::{KeyValueStoreDelegate, ProjectDelegate, WorktreeDelegate};
 use gpui::BackgroundExecutor;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use std::sync::{Arc, OnceLock};
 use wasmtime::component::{Linker, Resource};
 
 use super::latest;
 
-pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 5, 0);
+pub const MIN_VERSION: Version = Version::new(0, 5, 0);
 
 wasmtime::component::bindgen!({
     async: true,

crates/extension_host/src/wasm_host/wit/since_v0_6_0.rs 🔗

@@ -21,7 +21,7 @@ use futures::{FutureExt as _, io::BufReader};
 use gpui::{BackgroundExecutor, SharedString};
 use language::{BinaryStatus, LanguageName, language_settings::AllLanguageSettings};
 use project::project_settings::ProjectSettings;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use std::{
     env,
     net::Ipv4Addr,
@@ -36,8 +36,8 @@ use util::{
 };
 use wasmtime::component::{Linker, Resource};
 
-pub const MIN_VERSION: SemanticVersion = SemanticVersion::new(0, 6, 0);
-pub const MAX_VERSION: SemanticVersion = SemanticVersion::new(0, 7, 0);
+pub const MIN_VERSION: Version = Version::new(0, 6, 0);
+pub const MAX_VERSION: Version = Version::new(0, 7, 0);
 
 wasmtime::component::bindgen!({
     async: true,

crates/extensions_ui/Cargo.toml 🔗

@@ -28,7 +28,7 @@ num-format.workspace = true
 picker.workspace = true
 project.workspace = true
 release_channel.workspace = true
-semantic_version.workspace = true
+semver.workspace = true
 serde.workspace = true
 settings.workspace = true
 smallvec.workspace = true

crates/extensions_ui/src/extension_version_selector.rs 🔗

@@ -8,7 +8,7 @@ use fuzzy::{StringMatch, StringMatchCandidate, match_strings};
 use gpui::{App, DismissEvent, Entity, EventEmitter, Focusable, Task, WeakEntity, prelude::*};
 use picker::{Picker, PickerDelegate};
 use release_channel::ReleaseChannel;
-use semantic_version::SemanticVersion;
+use semver::Version;
 use settings::update_settings_file;
 use ui::{HighlightedLabel, ListItem, ListItemSpacing, prelude::*};
 use util::ResultExt;
@@ -60,8 +60,8 @@ impl ExtensionVersionSelectorDelegate {
         mut extension_versions: Vec<ExtensionMetadata>,
     ) -> Self {
         extension_versions.sort_unstable_by(|a, b| {
-            let a_version = SemanticVersion::from_str(&a.manifest.version);
-            let b_version = SemanticVersion::from_str(&b.manifest.version);
+            let a_version = Version::from_str(&a.manifest.version);
+            let b_version = Version::from_str(&b.manifest.version);
 
             match (a_version, b_version) {
                 (Ok(a_version), Ok(b_version)) => b_version.cmp(&a_version),

crates/gpui/Cargo.toml 🔗

@@ -121,7 +121,7 @@ usvg = { version = "0.45.0", default-features = false }
 util_macros.workspace = true
 schemars.workspace = true
 seahash = "4.1"
-semantic_version.workspace = true
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 slotmap.workspace = true

crates/gpui/src/platform.rs 🔗

@@ -76,7 +76,6 @@ pub use keystroke::*;
 pub(crate) use linux::*;
 #[cfg(target_os = "macos")]
 pub(crate) use mac::*;
-pub use semantic_version::SemanticVersion;
 #[cfg(any(test, feature = "test-support"))]
 pub(crate) use test::*;
 #[cfg(target_os = "windows")]

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

@@ -9,8 +9,7 @@ use crate::{
     CursorStyle, ForegroundExecutor, Image, ImageFormat, KeyContext, Keymap, MacDispatcher,
     MacDisplay, MacWindow, Menu, MenuItem, OsMenu, OwnedMenu, PathPromptOptions, Platform,
     PlatformDisplay, PlatformKeyboardLayout, PlatformKeyboardMapper, PlatformTextSystem,
-    PlatformWindow, Result, SemanticVersion, SystemMenuType, Task, WindowAppearance, WindowParams,
-    hash,
+    PlatformWindow, Result, SystemMenuType, Task, WindowAppearance, WindowParams, hash,
 };
 use anyhow::{Context as _, anyhow};
 use block::ConcreteBlock;
@@ -47,6 +46,7 @@ use objc::{
 };
 use parking_lot::Mutex;
 use ptr::null_mut;
+use semver::Version;
 use std::{
     cell::Cell,
     convert::TryInto,
@@ -389,7 +389,7 @@ impl MacPlatform {
                                     ns_string(key_to_native(keystroke.key()).as_ref()),
                                 )
                                 .autorelease();
-                            if Self::os_version() >= SemanticVersion::new(12, 0, 0) {
+                            if Self::os_version() >= Version::new(12, 0, 0) {
                                 let _: () = msg_send![item, setAllowsAutomaticKeyEquivalentLocalization: NO];
                             }
                             item.setKeyEquivalentModifierMask_(mask);
@@ -452,15 +452,15 @@ impl MacPlatform {
         }
     }
 
-    fn os_version() -> SemanticVersion {
+    fn os_version() -> Version {
         let version = unsafe {
             let process_info = NSProcessInfo::processInfo(nil);
             process_info.operatingSystemVersion()
         };
-        SemanticVersion::new(
-            version.majorVersion as usize,
-            version.minorVersion as usize,
-            version.patchVersion as usize,
+        Version::new(
+            version.majorVersion,
+            version.minorVersion,
+            version.patchVersion,
         )
     }
 }
@@ -668,7 +668,7 @@ impl Platform for MacPlatform {
         // API only available post Monterey
         // https://developer.apple.com/documentation/appkit/nsworkspace/3753004-setdefaultapplicationaturl
         let (done_tx, done_rx) = oneshot::channel();
-        if Self::os_version() < SemanticVersion::new(12, 0, 0) {
+        if Self::os_version() < Version::new(12, 0, 0) {
             return Task::ready(Err(anyhow!(
                 "macOS 12.0 or later is required to register URL schemes"
             )));
@@ -812,7 +812,7 @@ impl Platform for MacPlatform {
                                     // to break that use-case than breaking `a.sql`.
                                     if chunks.len() == 3
                                         && chunks[1].starts_with(chunks[2])
-                                        && Self::os_version() >= SemanticVersion::new(15, 0, 0)
+                                        && Self::os_version() >= Version::new(15, 0, 0)
                                     {
                                         let new_filename = OsStr::from_bytes(
                                             &filename.as_bytes()

crates/language_models/Cargo.toml 🔗

@@ -46,6 +46,7 @@ open_router = { workspace = true, features = ["schemars"] }
 partial-json-fixer.workspace = true
 release_channel.workspace = true
 schemars.workspace = true
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true

crates/language_models/src/provider/cloud.rs 🔗

@@ -15,9 +15,7 @@ use futures::{
     AsyncBufReadExt, FutureExt, Stream, StreamExt, future::BoxFuture, stream::BoxStream,
 };
 use google_ai::GoogleModelMode;
-use gpui::{
-    AnyElement, AnyView, App, AsyncApp, Context, Entity, SemanticVersion, Subscription, Task,
-};
+use gpui::{AnyElement, AnyView, App, AsyncApp, Context, Entity, Subscription, Task};
 use http_client::http::{HeaderMap, HeaderValue};
 use http_client::{AsyncBody, HttpClient, HttpRequestExt, Method, Response, StatusCode};
 use language_model::{
@@ -30,6 +28,7 @@ use language_model::{
 };
 use release_channel::AppVersion;
 use schemars::JsonSchema;
+use semver::Version;
 use serde::{Deserialize, Serialize, de::DeserializeOwned};
 use settings::SettingsStore;
 pub use settings::ZedDotDevAvailableModel as AvailableModel;
@@ -384,7 +383,7 @@ impl CloudLanguageModel {
     async fn perform_llm_completion(
         client: Arc<Client>,
         llm_api_token: LlmApiToken,
-        app_version: Option<SemanticVersion>,
+        app_version: Option<Version>,
         body: CompletionBody,
     ) -> Result<PerformLlmCompletionResponse> {
         let http_client = &client.http_client();
@@ -396,7 +395,7 @@ impl CloudLanguageModel {
             let request = http_client::Request::builder()
                 .method(Method::POST)
                 .uri(http_client.build_zed_llm_url("/completions", &[])?.as_ref())
-                .when_some(app_version, |builder, app_version| {
+                .when_some(app_version.as_ref(), |builder, app_version| {
                     builder.header(ZED_VERSION_HEADER_NAME, app_version.to_string())
                 })
                 .header("Content-Type", "application/json")

crates/language_tools/Cargo.toml 🔗

@@ -39,5 +39,6 @@ zed_actions.workspace = true
 editor = { workspace = true, features = ["test-support"] }
 release_channel.workspace = true
 gpui = { workspace = true, features = ["test-support"] }
+semver.workspace = true
 util = { workspace = true, features = ["test-support"] }
 zlog.workspace = true

crates/language_tools/src/lsp_log_view_tests.rs 🔗

@@ -4,7 +4,7 @@ use crate::lsp_log_view::LogMenuItem;
 
 use super::*;
 use futures::StreamExt;
-use gpui::{AppContext as _, SemanticVersion, TestAppContext, VisualTestContext};
+use gpui::{AppContext as _, TestAppContext, VisualTestContext};
 use language::{FakeLspAdapter, Language, LanguageConfig, LanguageMatcher, tree_sitter_rust};
 use lsp::LanguageServerName;
 use project::{
@@ -110,6 +110,6 @@ fn init_test(cx: &mut gpui::TestAppContext) {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
         theme::init(theme::LoadThemes::JustBase, cx);
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
 }

crates/lsp/Cargo.toml 🔗

@@ -36,5 +36,6 @@ release_channel.workspace = true
 async-pipe.workspace = true
 ctor.workspace = true
 gpui = { workspace = true, features = ["test-support"] }
+semver.workspace = true
 util = { workspace = true, features = ["test-support"] }
 zlog.workspace = true

crates/lsp/src/lsp.rs 🔗

@@ -1852,7 +1852,7 @@ impl FakeLanguageServer {
 #[cfg(test)]
 mod tests {
     use super::*;
-    use gpui::{SemanticVersion, TestAppContext};
+    use gpui::TestAppContext;
     use std::str::FromStr;
 
     #[ctor::ctor]
@@ -1863,7 +1863,7 @@ mod tests {
     #[gpui::test]
     async fn test_fake(cx: &mut TestAppContext) {
         cx.update(|cx| {
-            release_channel::init(SemanticVersion::default(), cx);
+            release_channel::init(semver::Version::new(0, 0, 0), cx);
         });
         let (server, mut fake) = FakeLanguageServer::new(
             LanguageServerId(0),

crates/project/src/project_tests.rs 🔗

@@ -20,7 +20,7 @@ use git::{
     status::{StatusCode, TrackedStatus},
 };
 use git2::RepositoryInitOptions;
-use gpui::{App, BackgroundExecutor, FutureExt, SemanticVersion, UpdateGlobal};
+use gpui::{App, BackgroundExecutor, FutureExt, UpdateGlobal};
 use itertools::Itertools;
 use language::{
     Diagnostic, DiagnosticEntry, DiagnosticEntryRef, DiagnosticSet, DiagnosticSourceKind,
@@ -10346,7 +10346,7 @@ pub fn init_test(cx: &mut gpui::TestAppContext) {
     cx.update(|cx| {
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
 }
 

crates/project_symbols/Cargo.toml 🔗

@@ -34,6 +34,7 @@ language = { workspace = true, features = ["test-support"] }
 lsp = { workspace = true, features = ["test-support"] }
 project = { workspace = true, features = ["test-support"] }
 release_channel.workspace = true
+semver.workspace = true
 settings = { workspace = true, features = ["test-support"] }
 theme = { workspace = true, features = ["test-support"] }
 workspace = { workspace = true, features = ["test-support"] }

crates/project_symbols/src/project_symbols.rs 🔗

@@ -289,7 +289,7 @@ impl PickerDelegate for ProjectSymbolsDelegate {
 mod tests {
     use super::*;
     use futures::StreamExt;
-    use gpui::{SemanticVersion, TestAppContext, VisualContext};
+    use gpui::{TestAppContext, VisualContext};
     use language::{FakeLspAdapter, Language, LanguageConfig, LanguageMatcher};
     use lsp::OneOf;
     use project::FakeFs;
@@ -438,7 +438,7 @@ mod tests {
             let store = SettingsStore::test(cx);
             cx.set_global(store);
             theme::init(theme::LoadThemes::JustBase, cx);
-            release_channel::init(SemanticVersion::default(), cx);
+            release_channel::init(semver::Version::new(0, 0, 0), cx);
             editor::init(cx);
         });
     }

crates/recent_projects/Cargo.toml 🔗

@@ -32,6 +32,7 @@ picker.workspace = true
 project.workspace = true
 release_channel.workspace = true
 remote.workspace = true
+semver.workspace = true
 serde.workspace = true
 settings.workspace = true
 smol.workspace = true

crates/recent_projects/src/remote_connections.rs 🔗

@@ -11,8 +11,7 @@ use extension_host::ExtensionStore;
 use futures::channel::oneshot;
 use gpui::{
     AnyWindowHandle, App, AsyncApp, DismissEvent, Entity, EventEmitter, Focusable, FontFeatures,
-    ParentElement as _, PromptLevel, Render, SemanticVersion, SharedString, Task,
-    TextStyleRefinement, WeakEntity,
+    ParentElement as _, PromptLevel, Render, SharedString, Task, TextStyleRefinement, WeakEntity,
 };
 
 use language::{CursorShape, Point};
@@ -22,6 +21,7 @@ use remote::{
     ConnectionIdentifier, RemoteClient, RemoteConnection, RemoteConnectionOptions, RemotePlatform,
     SshConnectionOptions,
 };
+use semver::Version;
 pub use settings::SshConnection;
 use settings::{ExtendingVec, RegisterSetting, Settings, WslConnection};
 use theme::ThemeSettings;
@@ -480,14 +480,14 @@ impl remote::RemoteClientDelegate for RemoteClientDelegate {
         &self,
         platform: RemotePlatform,
         release_channel: ReleaseChannel,
-        version: Option<SemanticVersion>,
+        version: Option<Version>,
         cx: &mut AsyncApp,
     ) -> Task<anyhow::Result<PathBuf>> {
         let this = self.clone();
         cx.spawn(async move |cx| {
             AutoUpdater::download_remote_server_release(
                 release_channel,
-                version,
+                version.clone(),
                 platform.os,
                 platform.arch,
                 move |status, cx| this.set_status(Some(status), cx),
@@ -498,6 +498,7 @@ impl remote::RemoteClientDelegate for RemoteClientDelegate {
                 format!(
                     "Downloading remote server binary (version: {}, os: {}, arch: {})",
                     version
+                        .as_ref()
                         .map(|v| format!("{}", v))
                         .unwrap_or("unknown".to_string()),
                     platform.os,
@@ -511,7 +512,7 @@ impl remote::RemoteClientDelegate for RemoteClientDelegate {
         &self,
         platform: RemotePlatform,
         release_channel: ReleaseChannel,
-        version: Option<SemanticVersion>,
+        version: Option<Version>,
         cx: &mut AsyncApp,
     ) -> Task<Result<Option<String>>> {
         cx.spawn(async move |cx| {

crates/release_channel/src/lib.rs 🔗

@@ -4,7 +4,8 @@
 
 use std::{env, str::FromStr, sync::LazyLock};
 
-use gpui::{App, Global, SemanticVersion};
+use gpui::{App, Global};
+use semver::Version;
 
 /// stable | dev | nightly | preview
 pub static RELEASE_CHANNEL_NAME: LazyLock<String> = LazyLock::new(|| {
@@ -70,7 +71,7 @@ impl AppCommitSha {
     }
 }
 
-struct GlobalAppVersion(SemanticVersion);
+struct GlobalAppVersion(Version);
 
 impl Global for GlobalAppVersion {}
 
@@ -79,20 +80,32 @@ pub struct AppVersion;
 
 impl AppVersion {
     /// Load the app version from env.
-    pub fn load(pkg_version: &str) -> SemanticVersion {
-        if let Ok(from_env) = env::var("ZED_APP_VERSION") {
+    pub fn load(
+        pkg_version: &str,
+        build_id: Option<&str>,
+        commit_sha: Option<AppCommitSha>,
+    ) -> Version {
+        let mut version: Version = if let Ok(from_env) = env::var("ZED_APP_VERSION") {
             from_env.parse().expect("invalid ZED_APP_VERSION")
         } else {
             pkg_version.parse().expect("invalid version in Cargo.toml")
+        };
+        if let Some(build_id) = build_id {
+            version.pre = semver::Prerelease::new(&build_id).expect("Invalid build identifier");
         }
+        if let Some(sha) = commit_sha {
+            version.build = semver::BuildMetadata::new(&sha.0).expect("Invalid build metadata");
+        }
+
+        version
     }
 
     /// Returns the global version number.
-    pub fn global(cx: &App) -> SemanticVersion {
+    pub fn global(cx: &App) -> Version {
         if cx.has_global::<GlobalAppVersion>() {
-            cx.global::<GlobalAppVersion>().0
+            cx.global::<GlobalAppVersion>().0.clone()
         } else {
-            SemanticVersion::default()
+            Version::new(0, 0, 0)
         }
     }
 }
@@ -121,13 +134,13 @@ struct GlobalReleaseChannel(ReleaseChannel);
 impl Global for GlobalReleaseChannel {}
 
 /// Initializes the release channel.
-pub fn init(app_version: SemanticVersion, cx: &mut App) {
+pub fn init(app_version: Version, cx: &mut App) {
     cx.set_global(GlobalAppVersion(app_version));
     cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL))
 }
 
 /// Initializes the release channel for tests that rely on fake release channel.
-pub fn init_test(app_version: SemanticVersion, release_channel: ReleaseChannel, cx: &mut App) {
+pub fn init_test(app_version: Version, release_channel: ReleaseChannel, cx: &mut App) {
     cx.set_global(GlobalAppVersion(app_version));
     cx.set_global(GlobalReleaseChannel(release_channel))
 }

crates/remote/Cargo.toml 🔗

@@ -32,6 +32,7 @@ prost.workspace = true
 release_channel.workspace = true
 rpc = { workspace = true, features = ["gpui"] }
 schemars.workspace = true
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true

crates/remote/src/remote_client.rs 🔗

@@ -22,7 +22,7 @@ use futures::{
 };
 use gpui::{
     App, AppContext as _, AsyncApp, BackgroundExecutor, BorrowAppContext, Context, Entity,
-    EventEmitter, FutureExt, Global, SemanticVersion, Task, WeakEntity,
+    EventEmitter, FutureExt, Global, Task, WeakEntity,
 };
 use parking_lot::Mutex;
 
@@ -31,6 +31,7 @@ use rpc::{
     AnyProtoClient, ErrorExt, ProtoClient, ProtoMessageHandlerSet, RpcError,
     proto::{self, Envelope, EnvelopedMessage, PeerId, RequestMessage, build_typed_envelope},
 };
+use semver::Version;
 use std::{
     collections::VecDeque,
     fmt,
@@ -71,14 +72,14 @@ pub trait RemoteClientDelegate: Send + Sync {
         &self,
         platform: RemotePlatform,
         release_channel: ReleaseChannel,
-        version: Option<SemanticVersion>,
+        version: Option<Version>,
         cx: &mut AsyncApp,
     ) -> Task<Result<Option<String>>>;
     fn download_server_binary_locally(
         &self,
         platform: RemotePlatform,
         release_channel: ReleaseChannel,
-        version: Option<SemanticVersion>,
+        version: Option<Version>,
         cx: &mut AsyncApp,
     ) -> Task<Result<PathBuf>>;
     fn set_status(&self, status: Option<&str>, cx: &mut AsyncApp);
@@ -1506,9 +1507,10 @@ mod fake {
         },
         select_biased,
     };
-    use gpui::{App, AppContext as _, AsyncApp, SemanticVersion, Task, TestAppContext};
+    use gpui::{App, AppContext as _, AsyncApp, Task, TestAppContext};
     use release_channel::ReleaseChannel;
     use rpc::proto::Envelope;
+    use semver::Version;
     use std::{path::PathBuf, sync::Arc};
     use util::paths::{PathStyle, RemotePathBuf};
 
@@ -1663,7 +1665,7 @@ mod fake {
             &self,
             _: RemotePlatform,
             _: ReleaseChannel,
-            _: Option<SemanticVersion>,
+            _: Option<Version>,
             _: &mut AsyncApp,
         ) -> Task<Result<PathBuf>> {
             unreachable!()
@@ -1673,7 +1675,7 @@ mod fake {
             &self,
             _platform: RemotePlatform,
             _release_channel: ReleaseChannel,
-            _version: Option<SemanticVersion>,
+            _version: Option<Version>,
             _cx: &mut AsyncApp,
         ) -> Task<Result<Option<String>>> {
             unreachable!()

crates/remote/src/transport/ssh.rs 🔗

@@ -10,11 +10,12 @@ use futures::{
     channel::mpsc::{Sender, UnboundedReceiver, UnboundedSender},
     select_biased,
 };
-use gpui::{App, AppContext as _, AsyncApp, SemanticVersion, Task};
+use gpui::{App, AppContext as _, AsyncApp, Task};
 use parking_lot::Mutex;
 use paths::remote_server_dir_relative;
-use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
+use release_channel::{AppVersion, ReleaseChannel};
 use rpc::proto::Envelope;
+use semver::Version;
 pub use settings::SshPortForwardOption;
 use smol::{
     fs,
@@ -515,15 +516,10 @@ impl SshRemoteConnection {
             ssh_default_system_shell,
         };
 
-        let (release_channel, version, commit) = cx.update(|cx| {
-            (
-                ReleaseChannel::global(cx),
-                AppVersion::global(cx),
-                AppCommitSha::try_global(cx),
-            )
-        })?;
+        let (release_channel, version) =
+            cx.update(|cx| (ReleaseChannel::global(cx), AppVersion::global(cx)))?;
         this.remote_binary_path = Some(
-            this.ensure_server_binary(&delegate, release_channel, version, commit, cx)
+            this.ensure_server_binary(&delegate, release_channel, version, cx)
                 .await?,
         );
 
@@ -534,15 +530,10 @@ impl SshRemoteConnection {
         &self,
         delegate: &Arc<dyn RemoteClientDelegate>,
         release_channel: ReleaseChannel,
-        version: SemanticVersion,
-        commit: Option<AppCommitSha>,
+        version: Version,
         cx: &mut AsyncApp,
     ) -> Result<Arc<RelPath>> {
         let version_str = match release_channel {
-            ReleaseChannel::Nightly => {
-                let commit = commit.map(|s| s.full()).unwrap_or_default();
-                format!("{}-{}", version, commit)
-            }
             ReleaseChannel::Dev => "build".to_string(),
             _ => version.to_string(),
         };
@@ -609,7 +600,12 @@ impl SshRemoteConnection {
         );
         if !self.socket.connection_options.upload_binary_over_ssh
             && let Some(url) = delegate
-                .get_download_url(self.ssh_platform, release_channel, wanted_version, cx)
+                .get_download_url(
+                    self.ssh_platform,
+                    release_channel,
+                    wanted_version.clone(),
+                    cx,
+                )
                 .await?
         {
             match self
@@ -631,7 +627,12 @@ impl SshRemoteConnection {
         }
 
         let src_path = delegate
-            .download_server_binary_locally(self.ssh_platform, release_channel, wanted_version, cx)
+            .download_server_binary_locally(
+                self.ssh_platform,
+                release_channel,
+                wanted_version.clone(),
+                cx,
+            )
             .await
             .context("downloading server binary locally")?;
         self.upload_local_server_binary(&src_path, &tmp_path_gz, delegate, cx)

crates/remote/src/transport/wsl.rs 🔗

@@ -6,9 +6,10 @@ use anyhow::{Context, Result, anyhow, bail};
 use async_trait::async_trait;
 use collections::HashMap;
 use futures::channel::mpsc::{Sender, UnboundedReceiver, UnboundedSender};
-use gpui::{App, AppContext as _, AsyncApp, SemanticVersion, Task};
-use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
+use gpui::{App, AppContext as _, AsyncApp, Task};
+use release_channel::{AppVersion, ReleaseChannel};
 use rpc::proto::Envelope;
+use semver::Version;
 use smol::{fs, process};
 use std::{
     ffi::OsStr,
@@ -62,13 +63,8 @@ impl WslRemoteConnection {
             connection_options.distro_name,
             connection_options.user
         );
-        let (release_channel, version, commit) = cx.update(|cx| {
-            (
-                ReleaseChannel::global(cx),
-                AppVersion::global(cx),
-                AppCommitSha::try_global(cx),
-            )
-        })?;
+        let (release_channel, version) =
+            cx.update(|cx| (ReleaseChannel::global(cx), AppVersion::global(cx)))?;
 
         let mut this = Self {
             connection_options,
@@ -94,7 +90,7 @@ impl WslRemoteConnection {
             .context("failed detecting platform")?;
         log::info!("Remote platform discovered: {:?}", this.platform);
         this.remote_binary_path = Some(
-            this.ensure_server_binary(&delegate, release_channel, version, commit, cx)
+            this.ensure_server_binary(&delegate, release_channel, version, cx)
                 .await
                 .context("failed ensuring server binary")?,
         );
@@ -157,15 +153,10 @@ impl WslRemoteConnection {
         &self,
         delegate: &Arc<dyn RemoteClientDelegate>,
         release_channel: ReleaseChannel,
-        version: SemanticVersion,
-        commit: Option<AppCommitSha>,
+        version: Version,
         cx: &mut AsyncApp,
     ) -> Result<Arc<RelPath>> {
         let version_str = match release_channel {
-            ReleaseChannel::Nightly => {
-                let commit = commit.map(|s| s.full()).unwrap_or_default();
-                format!("{}-{}", version, commit)
-            }
             ReleaseChannel::Dev => "build".to_string(),
             _ => version.to_string(),
         };

crates/remote_server/Cargo.toml 🔗

@@ -55,6 +55,7 @@ remote.workspace = true
 reqwest_client.workspace = true
 rpc.workspace = true
 rust-embed = { workspace = true, optional = true, features = ["debug-embed"] }
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true

crates/remote_server/build.rs 🔗

@@ -28,4 +28,7 @@ fn main() {
 
         println!("cargo:rustc-env=ZED_COMMIT_SHA={git_sha}");
     }
+    if let Some(build_identifier) = option_env!("GITHUB_RUN_NUMBER") {
+        println!("cargo:rustc-env=ZED_BUILD_ID={build_identifier}");
+    }
 }

crates/remote_server/src/remote_editing_tests.rs 🔗

@@ -11,7 +11,7 @@ use prompt_store::ProjectContext;
 
 use extension::ExtensionHostProxy;
 use fs::{FakeFs, Fs};
-use gpui::{AppContext as _, Entity, SemanticVersion, SharedString, TestAppContext};
+use gpui::{AppContext as _, Entity, SharedString, TestAppContext};
 use http_client::{BlockedHttpClient, FakeHttpClient};
 use language::{
     Buffer, FakeLspAdapter, LanguageConfig, LanguageMatcher, LanguageRegistry, LineEnding,
@@ -1503,7 +1503,7 @@ async fn test_remote_git_diffs_when_recv_update_repository_delay(
         let settings_store = SettingsStore::test(cx);
         cx.set_global(settings_store);
         theme::init(theme::LoadThemes::JustBase, cx);
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
         editor::init(cx);
     });
 
@@ -1910,10 +1910,10 @@ pub async fn init_test(
 ) -> (Entity<Project>, Entity<HeadlessProject>) {
     let server_fs = server_fs.clone();
     cx.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
     server_cx.update(|cx| {
-        release_channel::init(SemanticVersion::default(), cx);
+        release_channel::init(semver::Version::new(0, 0, 0), cx);
     });
     init_logger();
 

crates/remote_server/src/remote_server.rs 🔗

@@ -71,10 +71,14 @@ pub fn run(command: Commands) -> anyhow::Result<()> {
                     println!("{}", env!("ZED_PKG_VERSION"))
                 }
                 ReleaseChannel::Nightly | ReleaseChannel::Dev => {
-                    println!(
-                        "{}",
-                        option_env!("ZED_COMMIT_SHA").unwrap_or(release_channel.dev_name())
-                    )
+                    let commit_sha =
+                        option_env!("ZED_COMMIT_SHA").unwrap_or(release_channel.dev_name());
+                    let build_id = option_env!("ZED_BUILD_ID");
+                    if let Some(build_id) = build_id {
+                        println!("{}+{}", build_id, commit_sha)
+                    } else {
+                        println!("{commit_sha}");
+                    }
                 }
             };
             Ok(())

crates/remote_server/src/unix.rs 🔗

@@ -9,7 +9,7 @@ use fs::{Fs, RealFs};
 use futures::channel::{mpsc, oneshot};
 use futures::{AsyncRead, AsyncWrite, AsyncWriteExt, FutureExt, SinkExt, select, select_biased};
 use git::GitHostingProviderRegistry;
-use gpui::{App, AppContext as _, Context, Entity, SemanticVersion, UpdateGlobal as _};
+use gpui::{App, AppContext as _, Context, Entity, UpdateGlobal as _};
 use gpui_tokio::Tokio;
 use http_client::{Url, read_proxy_from_env};
 use language::LanguageRegistry;
@@ -19,7 +19,7 @@ use project::project_settings::ProjectSettings;
 use util::command::new_smol_command;
 
 use proto::CrashReport;
-use release_channel::{AppVersion, RELEASE_CHANNEL, ReleaseChannel};
+use release_channel::{AppCommitSha, AppVersion, RELEASE_CHANNEL, ReleaseChannel};
 use remote::RemoteClient;
 use remote::{
     json_log::LogRecord,
@@ -48,10 +48,16 @@ use std::{
 };
 use thiserror::Error;
 
-pub static VERSION: LazyLock<&str> = LazyLock::new(|| match *RELEASE_CHANNEL {
-    ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION"),
+pub static VERSION: LazyLock<String> = LazyLock::new(|| match *RELEASE_CHANNEL {
+    ReleaseChannel::Stable | ReleaseChannel::Preview => env!("ZED_PKG_VERSION").to_owned(),
     ReleaseChannel::Nightly | ReleaseChannel::Dev => {
-        option_env!("ZED_COMMIT_SHA").unwrap_or("missing-zed-commit-sha")
+        let commit_sha = option_env!("ZED_COMMIT_SHA").unwrap_or("missing-zed-commit-sha");
+        let build_identifier = option_env!("ZED_BUILD_ID");
+        if let Some(build_id) = build_identifier {
+            format!("{build_id}+{commit_sha}")
+        } else {
+            commit_sha.to_owned()
+        }
     }
 });
 
@@ -390,7 +396,12 @@ pub fn execute_run(
     let git_hosting_provider_registry = Arc::new(GitHostingProviderRegistry::new());
     app.run(move |cx| {
         settings::init(cx);
-        let app_version = AppVersion::load(env!("ZED_PKG_VERSION"));
+        let app_commit_sha = option_env!("ZED_COMMIT_SHA").map(|s| AppCommitSha::new(s.to_owned()));
+        let app_version = AppVersion::load(
+            env!("ZED_PKG_VERSION"),
+            option_env!("ZED_BUILD_ID"),
+            app_commit_sha,
+        );
         release_channel::init(app_version, cx);
         gpui_tokio::init(cx);
 
@@ -1002,9 +1013,9 @@ fn cleanup_old_binaries() -> Result<()> {
 }
 
 fn is_new_version(version: &str) -> bool {
-    SemanticVersion::from_str(version)
+    semver::Version::from_str(version)
         .ok()
-        .zip(SemanticVersion::from_str(env!("ZED_PKG_VERSION")).ok())
+        .zip(semver::Version::from_str(env!("ZED_PKG_VERSION")).ok())
         .is_some_and(|(version, current_version)| version >= current_version)
 }
 

crates/semantic_version/Cargo.toml 🔗

@@ -1,17 +0,0 @@
-[package]
-name = "semantic_version"
-version = "0.1.0"
-edition.workspace = true
-publish = false
-license = "Apache-2.0"
-description = "A library for working with semantic versioning in gpui and Zed"
-
-[lints]
-workspace = true
-
-[lib]
-path = "src/semantic_version.rs"
-
-[dependencies]
-anyhow.workspace = true
-serde.workspace = true

crates/semantic_version/src/semantic_version.rs 🔗

@@ -1,99 +0,0 @@
-//! Constructs for working with [semantic versions](https://semver.org/).
-
-#![deny(missing_docs)]
-
-use std::{
-    fmt::{self, Display},
-    str::FromStr,
-};
-
-use anyhow::{Context as _, Result};
-use serde::{Deserialize, Serialize, de::Error};
-
-/// A [semantic version](https://semver.org/) number.
-#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd)]
-pub struct SemanticVersion {
-    major: usize,
-    minor: usize,
-    patch: usize,
-}
-
-impl SemanticVersion {
-    /// Returns a new [`SemanticVersion`] from the given components.
-    pub const fn new(major: usize, minor: usize, patch: usize) -> Self {
-        Self {
-            major,
-            minor,
-            patch,
-        }
-    }
-
-    /// Returns the major version number.
-    #[inline(always)]
-    pub fn major(&self) -> usize {
-        self.major
-    }
-
-    /// Returns the minor version number.
-    #[inline(always)]
-    pub fn minor(&self) -> usize {
-        self.minor
-    }
-
-    /// Returns the patch version number.
-    #[inline(always)]
-    pub fn patch(&self) -> usize {
-        self.patch
-    }
-}
-
-impl FromStr for SemanticVersion {
-    type Err = anyhow::Error;
-
-    fn from_str(s: &str) -> Result<Self> {
-        let mut components = s.trim().split('.');
-        let major = components
-            .next()
-            .context("missing major version number")?
-            .parse()?;
-        let minor = components
-            .next()
-            .context("missing minor version number")?
-            .parse()?;
-        let patch = components
-            .next()
-            .context("missing patch version number")?
-            .parse()?;
-        Ok(Self {
-            major,
-            minor,
-            patch,
-        })
-    }
-}
-
-impl Display for SemanticVersion {
-    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
-        write!(f, "{}.{}.{}", self.major, self.minor, self.patch)
-    }
-}
-
-impl Serialize for SemanticVersion {
-    fn serialize<S>(&self, serializer: S) -> std::prelude::v1::Result<S::Ok, S::Error>
-    where
-        S: serde::Serializer,
-    {
-        serializer.serialize_str(&self.to_string())
-    }
-}
-
-impl<'de> Deserialize<'de> for SemanticVersion {
-    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
-    where
-        D: serde::Deserializer<'de>,
-    {
-        let string = String::deserialize(deserializer)?;
-        Self::from_str(&string)
-            .map_err(|_| Error::custom(format!("Invalid version string \"{string}\"")))
-    }
-}

crates/system_specs/Cargo.toml 🔗

@@ -20,6 +20,7 @@ client.workspace = true
 gpui.workspace = true
 human_bytes.workspace = true
 release_channel.workspace = true
+semver.workspace = true
 serde.workspace = true
 sysinfo.workspace = true
 

crates/system_specs/src/system_specs.rs 🔗

@@ -1,10 +1,9 @@
-//! # system_specs
-
 use client::telemetry;
 pub use gpui::GpuSpecs;
-use gpui::{App, AppContext as _, SemanticVersion, Task, Window, actions};
+use gpui::{App, AppContext as _, Task, Window, actions};
 use human_bytes::human_bytes;
 use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
+use semver::Version;
 use serde::Serialize;
 use std::{env, fmt::Display};
 use sysinfo::{MemoryRefreshKind, RefreshKind, System};
@@ -72,7 +71,7 @@ impl SystemSpecs {
     }
 
     pub fn new_stateless(
-        app_version: SemanticVersion,
+        app_version: Version,
         app_commit_sha: Option<AppCommitSha>,
         release_channel: ReleaseChannel,
     ) -> Self {

crates/telemetry_events/Cargo.toml 🔗

@@ -12,6 +12,6 @@ workspace = true
 path = "src/telemetry_events.rs"
 
 [dependencies]
-semantic_version.workspace = true
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true

crates/telemetry_events/src/telemetry_events.rs 🔗

@@ -1,6 +1,6 @@
 //! See [Telemetry in Zed](https://zed.dev/docs/telemetry) for additional information.
 
-use semantic_version::SemanticVersion;
+use semver::Version;
 use serde::{Deserialize, Serialize};
 use std::{collections::HashMap, fmt::Display, time::Duration};
 
@@ -28,7 +28,7 @@ pub struct EventRequestBody {
 }
 
 impl EventRequestBody {
-    pub fn semver(&self) -> Option<SemanticVersion> {
+    pub fn semver(&self) -> Option<Version> {
         self.app_version.parse().ok()
     }
 }

crates/vim/Cargo.toml 🔗

@@ -66,6 +66,7 @@ lsp = { workspace = true, features = ["test-support"] }
 parking_lot.workspace = true
 project_panel.workspace = true
 release_channel.workspace = true
+semver.workspace = true
 settings_ui.workspace = true
 settings.workspace = true
 perf.workspace = true

crates/vim/src/test/vim_test_context.rs 🔗

@@ -1,8 +1,9 @@
 use std::ops::{Deref, DerefMut};
 
 use editor::test::editor_lsp_test_context::EditorLspTestContext;
-use gpui::{Context, Entity, SemanticVersion, UpdateGlobal};
+use gpui::{Context, Entity, UpdateGlobal};
 use search::{BufferSearchBar, project_search::ProjectSearchBar};
+use semver::Version;
 
 use crate::{state::Operator, *};
 
@@ -19,7 +20,7 @@ impl VimTestContext {
         cx.update(|cx| {
             let settings = SettingsStore::test(cx);
             cx.set_global(settings);
-            release_channel::init(SemanticVersion::default(), cx);
+            release_channel::init(Version::new(0, 0, 0), cx);
             command_palette::init(cx);
             project_panel::init(cx);
             git_ui::init(cx);

crates/zed/Cargo.toml 🔗

@@ -186,6 +186,7 @@ itertools.workspace = true
 language = { workspace = true, features = ["test-support"] }
 pretty_assertions.workspace = true
 project = { workspace = true, features = ["test-support"] }
+semver.workspace = true
 terminal_view = { workspace = true, features = ["test-support"] }
 tree-sitter-md.workspace = true
 tree-sitter-rust.workspace = true

crates/zed/build.rs 🔗

@@ -32,6 +32,10 @@ fn main() {
 
         println!("cargo:rustc-env=ZED_COMMIT_SHA={git_sha}");
 
+        if let Some(build_identifier) = option_env!("GITHUB_RUN_NUMBER") {
+            println!("cargo:rustc-env=ZED_BUILD_ID={build_identifier}");
+        }
+
         if let Ok(build_profile) = std::env::var("PROFILE")
             && build_profile == "release"
         {

crates/zed/src/main.rs 🔗

@@ -250,9 +250,10 @@ pub fn main() {
         };
     }
 
-    let app_version = AppVersion::load(env!("CARGO_PKG_VERSION"));
+    let version = option_env!("ZED_BUILD_ID");
     let app_commit_sha =
         option_env!("ZED_COMMIT_SHA").map(|commit_sha| AppCommitSha::new(commit_sha.to_string()));
+    let app_version = AppVersion::load(env!("CARGO_PKG_VERSION"), version, app_commit_sha.clone());
 
     if args.system_specs {
         let system_specs = system_specs::SystemSpecs::new_stateless(

crates/zed/src/zed.rs 🔗

@@ -53,7 +53,7 @@ use project_panel::ProjectPanel;
 use prompt_store::PromptBuilder;
 use quick_action_bar::QuickActionBar;
 use recent_projects::open_remote_project;
-use release_channel::{AppCommitSha, ReleaseChannel};
+use release_channel::{AppCommitSha, AppVersion, ReleaseChannel};
 use rope::Rope;
 use search::project_search::ProjectSearchBar;
 use settings::{
@@ -1168,7 +1168,9 @@ fn initialize_pane(
 }
 
 fn about(_: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
+    use std::fmt::Write;
     let release_channel = ReleaseChannel::global(cx).display_name();
+    let full_version = AppVersion::global(cx);
     let version = env!("CARGO_PKG_VERSION");
     let debug = if cfg!(debug_assertions) {
         "(debug)"
@@ -1176,7 +1178,16 @@ fn about(_: &mut Workspace, window: &mut Window, cx: &mut Context<Workspace>) {
         ""
     };
     let message = format!("{release_channel} {version} {debug}");
-    let detail = AppCommitSha::try_global(cx).map(|sha| sha.full());
+
+    let mut detail = AppCommitSha::try_global(cx)
+        .map(|sha| sha.full())
+        .unwrap_or_default();
+    if !detail.is_empty() {
+        detail.push('\n');
+    }
+    _ = write!(&mut detail, "\n{full_version}");
+
+    let detail = Some(detail);
 
     let prompt = window.prompt(
         PromptLevel::Info,
@@ -2235,12 +2246,13 @@ mod tests {
         DisplayPoint, Editor, MultiBufferOffset, SelectionEffects, display_map::DisplayRow,
     };
     use gpui::{
-        Action, AnyWindowHandle, App, AssetSource, BorrowAppContext, SemanticVersion,
-        TestAppContext, UpdateGlobal, VisualTestContext, WindowHandle, actions,
+        Action, AnyWindowHandle, App, AssetSource, BorrowAppContext, TestAppContext, UpdateGlobal,
+        VisualTestContext, WindowHandle, actions,
     };
     use language::{LanguageMatcher, LanguageRegistry};
     use pretty_assertions::{assert_eq, assert_ne};
     use project::{Project, ProjectPath};
+    use semver::Version;
     use serde_json::json;
     use settings::{SettingsStore, watch_config_file};
     use std::{
@@ -4777,7 +4789,7 @@ mod tests {
             call::init(app_state.client.clone(), app_state.user_store.clone(), cx);
             notifications::init(app_state.client.clone(), app_state.user_store.clone(), cx);
             workspace::init(app_state.clone(), cx);
-            release_channel::init(SemanticVersion::default(), cx);
+            release_channel::init(Version::new(0, 0, 0), cx);
             command_palette::init(cx);
             editor::init(cx);
             collab_ui::init(&app_state, cx);

crates/zeta/Cargo.toml 🔗

@@ -44,6 +44,7 @@ project.workspace = true
 rand.workspace = true
 regex.workspace = true
 release_channel.workspace = true
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 settings.workspace = true

crates/zeta/src/zeta.rs 🔗

@@ -27,8 +27,8 @@ use cloud_llm_client::{
 use collections::{HashMap, HashSet, VecDeque};
 use futures::AsyncReadExt;
 use gpui::{
-    App, AppContext as _, AsyncApp, Context, Entity, EntityId, Global, SemanticVersion,
-    SharedString, Subscription, Task, actions,
+    App, AppContext as _, AsyncApp, Context, Entity, EntityId, Global, SharedString, Subscription,
+    Task, actions,
 };
 use http_client::{AsyncBody, HttpClient, Method, Request, Response};
 use input_excerpt::excerpt_for_cursor_position;
@@ -38,6 +38,7 @@ use language::{
 use language_model::{LlmApiToken, RefreshLlmTokenListener};
 use project::{Project, ProjectPath};
 use release_channel::AppVersion;
+use semver::Version;
 use settings::WorktreeId;
 use std::collections::hash_map;
 use std::mem;
@@ -608,7 +609,7 @@ impl Zeta {
                 if let Some(minimum_required_version) = response
                     .headers()
                     .get(MINIMUM_REQUIRED_VERSION_HEADER_NAME)
-                    .and_then(|version| SemanticVersion::from_str(version.to_str().ok()?).ok())
+                    .and_then(|version| Version::from_str(version.to_str().ok()?).ok())
                 {
                     anyhow::ensure!(
                         app_version >= minimum_required_version,
@@ -683,7 +684,7 @@ impl Zeta {
             if let Some(minimum_required_version) = response
                 .headers()
                 .get(MINIMUM_REQUIRED_VERSION_HEADER_NAME)
-                .and_then(|version| SemanticVersion::from_str(version.to_str().ok()?).ok())
+                .and_then(|version| Version::from_str(version.to_str().ok()?).ok())
                 && app_version < minimum_required_version
             {
                 return Err(anyhow!(ZedUpdateRequiredError {
@@ -752,7 +753,7 @@ impl Zeta {
             if let Some(minimum_required_version) = response
                 .headers()
                 .get(MINIMUM_REQUIRED_VERSION_HEADER_NAME)
-                .and_then(|version| SemanticVersion::from_str(version.to_str().ok()?).ok())
+                .and_then(|version| Version::from_str(version.to_str().ok()?).ok())
                 && app_version < minimum_required_version
             {
                 return Err(anyhow!(ZedUpdateRequiredError {
@@ -1115,7 +1116,7 @@ impl Zeta {
 pub struct PerformPredictEditsParams {
     pub client: Arc<Client>,
     pub llm_token: LlmApiToken,
-    pub app_version: SemanticVersion,
+    pub app_version: Version,
     pub body: PredictEditsBody,
 }
 
@@ -1124,7 +1125,7 @@ pub struct PerformPredictEditsParams {
     "You must update to Zed version {minimum_version} or higher to continue using edit predictions."
 )]
 pub struct ZedUpdateRequiredError {
-    minimum_version: SemanticVersion,
+    minimum_version: Version,
 }
 
 fn common_prefix<T1: Iterator<Item = char>, T2: Iterator<Item = char>>(a: T1, b: T2) -> usize {

crates/zeta2/Cargo.toml 🔗

@@ -37,6 +37,7 @@ open_ai.workspace = true
 pretty_assertions.workspace = true
 project.workspace = true
 release_channel.workspace = true
+semver.workspace = true
 serde.workspace = true
 serde_json.workspace = true
 smol.workspace = true

crates/zeta2/src/zeta2.rs 🔗

@@ -20,8 +20,8 @@ use futures::AsyncReadExt as _;
 use futures::channel::{mpsc, oneshot};
 use gpui::http_client::{AsyncBody, Method};
 use gpui::{
-    App, AsyncApp, Entity, EntityId, Global, SemanticVersion, SharedString, Subscription, Task,
-    WeakEntity, http_client, prelude::*,
+    App, AsyncApp, Entity, EntityId, Global, SharedString, Subscription, Task, WeakEntity,
+    http_client, prelude::*,
 };
 use language::{Anchor, Buffer, DiagnosticSet, LanguageServerId, Point, ToOffset as _, ToPoint};
 use language::{BufferSnapshot, OffsetRangeExt};
@@ -30,13 +30,14 @@ use lsp::DiagnosticSeverity;
 use open_ai::FunctionDefinition;
 use project::{Project, ProjectPath};
 use release_channel::AppVersion;
+use semver::Version;
 use serde::de::DeserializeOwned;
 use std::collections::{VecDeque, hash_map};
 
 use std::fmt::Write;
 use std::ops::Range;
 use std::path::Path;
-use std::str::FromStr as _;
+use std::str::FromStr;
 use std::sync::{Arc, LazyLock};
 use std::time::{Duration, Instant};
 use std::{env, mem};
@@ -1696,7 +1697,7 @@ impl Zeta {
         request: open_ai::Request,
         client: Arc<Client>,
         llm_token: LlmApiToken,
-        app_version: SemanticVersion,
+        app_version: Version,
         #[cfg(feature = "eval-support")] eval_cache: Option<Arc<dyn EvalCache>>,
         #[cfg(feature = "eval-support")] eval_cache_kind: EvalCacheEntryKind,
     ) -> Result<(open_ai::Response, Option<EditPredictionUsage>)> {
@@ -1798,7 +1799,7 @@ impl Zeta {
         build: impl Fn(http_client::http::request::Builder) -> Result<http_client::Request<AsyncBody>>,
         client: Arc<Client>,
         llm_token: LlmApiToken,
-        app_version: SemanticVersion,
+        app_version: Version,
     ) -> Result<(Res, Option<EditPredictionUsage>)>
     where
         Res: DeserializeOwned,
@@ -1822,7 +1823,7 @@ impl Zeta {
             if let Some(minimum_required_version) = response
                 .headers()
                 .get(MINIMUM_REQUIRED_VERSION_HEADER_NAME)
-                .and_then(|version| SemanticVersion::from_str(version.to_str().ok()?).ok())
+                .and_then(|version| Version::from_str(version.to_str().ok()?).ok())
             {
                 anyhow::ensure!(
                     app_version >= minimum_required_version,
@@ -2310,7 +2311,7 @@ pub fn text_from_response(mut res: open_ai::Response) -> Option<String> {
     "You must update to Zed version {minimum_version} or higher to continue using edit predictions."
 )]
 pub struct ZedUpdateRequiredError {
-    minimum_version: SemanticVersion,
+    minimum_version: Version,
 }
 
 fn make_syntax_context_cloud_request(

crates/zeta_cli/src/headless.rs 🔗

@@ -8,7 +8,7 @@ use language::LanguageRegistry;
 use language_extension::LspAccess;
 use node_runtime::{NodeBinaryOptions, NodeRuntime};
 use project::project_settings::ProjectSettings;
-use release_channel::AppVersion;
+use release_channel::{AppCommitSha, AppVersion};
 use reqwest_client::ReqwestClient;
 use settings::{Settings, SettingsStore};
 use std::path::PathBuf;
@@ -26,8 +26,14 @@ pub struct ZetaCliAppState {
 
 // TODO: dedupe with crates/eval/src/eval.rs
 pub fn init(cx: &mut App) -> ZetaCliAppState {
-    let app_version = AppVersion::load(env!("ZED_PKG_VERSION"));
-    release_channel::init(app_version, cx);
+    let app_commit_sha = option_env!("ZED_COMMIT_SHA").map(|s| AppCommitSha::new(s.to_owned()));
+
+    let app_version = AppVersion::load(
+        env!("ZED_PKG_VERSION"),
+        option_env!("ZED_BUILD_ID"),
+        app_commit_sha,
+    );
+    release_channel::init(app_version.clone(), cx);
     gpui_tokio::init(cx);
 
     let settings_store = SettingsStore::new(cx, &settings::default_settings());

script/upload-nightly 🔗

@@ -4,14 +4,14 @@ bash -euo pipefail
 source script/lib/blob-store.sh
 
 bucket_name="zed-nightly-host"
+version=$(./script/get-crate-version zed)-"${GITHUB_RUN_NUMBER}+${GITHUB_SHA}"
 
 for file_to_upload in ./release-artifacts/*; do
     [ -f "$file_to_upload" ] || continue
     upload_to_blob_store_public $bucket_name "$file_to_upload" "nightly/$(basename "$file_to_upload")"
-    upload_to_blob_store_public $bucket_name "$file_to_upload" "${GITHUB_SHA}/$(basename "$file_to_upload")"
+    upload_to_blob_store_public $bucket_name "$file_to_upload" "${version}/$(basename "$file_to_upload")"
     rm -f "$file_to_upload"
 done
 
-sha=$(git rev-parse HEAD)
-echo -n ${sha} > ./release-artifacts/latest-sha
+echo -n ${version} > ./release-artifacts/latest-sha
 upload_to_blob_store_public $bucket_name "release-artifacts/latest-sha" "nightly/latest-sha"