Cargo.lock 🔗
@@ -1486,6 +1486,7 @@ dependencies = [
"prometheus",
"prost 0.8.0",
"rand 0.8.5",
+ "release_channel",
"reqwest",
"rpc",
"scrypt",
Conrad Irwin created
- Send app version and release stage to collab on connect
- Read the new header on the server
Release Notes:
- Added the ability to collaborate with users on different releases of
Zed.
Cargo.lock | 1
crates/auto_update/src/auto_update.rs | 41 ++++++++++-----------
crates/channel/src/channel_store_tests.rs | 1
crates/client/src/client.rs | 19 ++++++---
crates/collab/Cargo.toml | 1
crates/collab/src/rpc.rs | 30 ++++++++++++++++
crates/collab/src/tests/test_server.rs | 1
crates/feedback/src/system_specs.rs | 22 ++++-------
crates/gpui/src/platform.rs | 45 +----------------------
crates/release_channel/src/lib.rs | 37 ++++++++++++++++---
crates/settings/src/settings_store.rs | 4 +-
crates/util/src/semantic_version.rs | 46 +++++++++++++++++++++++++
crates/util/src/util.rs | 2 +
crates/zed/src/main.rs | 14 ++++--
14 files changed, 167 insertions(+), 97 deletions(-)
@@ -1486,6 +1486,7 @@ dependencies = [
"prometheus",
"prost 0.8.0",
"rand 0.8.5",
+ "release_channel",
"reqwest",
"rpc",
"scrypt",
@@ -1,7 +1,7 @@
mod update_notification;
use anyhow::{anyhow, Context, Result};
-use client::{Client, TelemetrySettings, ZED_APP_PATH, ZED_APP_VERSION};
+use client::{Client, TelemetrySettings, ZED_APP_PATH};
use db::kvp::KEY_VALUE_STORE;
use db::RELEASE_CHANNEL;
use gpui::{
@@ -108,29 +108,28 @@ pub fn init(http_client: Arc<ZedHttpClient>, cx: &mut AppContext) {
})
.detach();
- if let Some(version) = ZED_APP_VERSION.or_else(|| cx.app_metadata().app_version) {
- let auto_updater = cx.new_model(|cx| {
- let updater = AutoUpdater::new(version, http_client);
+ let version = release_channel::AppVersion::global(cx);
+ let auto_updater = cx.new_model(|cx| {
+ let updater = AutoUpdater::new(version, http_client);
- let mut update_subscription = AutoUpdateSetting::get_global(cx)
- .0
- .then(|| updater.start_polling(cx));
-
- cx.observe_global::<SettingsStore>(move |updater, cx| {
- if AutoUpdateSetting::get_global(cx).0 {
- if update_subscription.is_none() {
- update_subscription = Some(updater.start_polling(cx))
- }
- } else {
- update_subscription.take();
+ let mut update_subscription = AutoUpdateSetting::get_global(cx)
+ .0
+ .then(|| updater.start_polling(cx));
+
+ cx.observe_global::<SettingsStore>(move |updater, cx| {
+ if AutoUpdateSetting::get_global(cx).0 {
+ if update_subscription.is_none() {
+ update_subscription = Some(updater.start_polling(cx))
}
- })
- .detach();
+ } else {
+ update_subscription.take();
+ }
+ })
+ .detach();
- updater
- });
- cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
- }
+ updater
+ });
+ cx.set_global(GlobalAutoUpdate(Some(auto_updater)));
}
pub fn check(_: &Check, cx: &mut WindowContext) {
@@ -329,6 +329,7 @@ async fn test_channel_messages(cx: &mut TestAppContext) {
fn init_test(cx: &mut AppContext) -> Model<ChannelStore> {
let settings_store = SettingsStore::test(cx);
cx.set_global(settings_store);
+ release_channel::init("0.0.0", cx);
client::init_settings(cx);
let http = FakeHttpClient::with_404_response();
@@ -15,14 +15,13 @@ use futures::{
TryFutureExt as _, TryStreamExt,
};
use gpui::{
- actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, SemanticVersion,
- Task, WeakModel,
+ actions, AnyModel, AnyWeakModel, AppContext, AsyncAppContext, Global, Model, Task, WeakModel,
};
use lazy_static::lazy_static;
use parking_lot::RwLock;
use postage::watch;
use rand::prelude::*;
-use release_channel::ReleaseChannel;
+use release_channel::{AppVersion, ReleaseChannel};
use rpc::proto::{AnyTypedEnvelope, EntityMessage, EnvelopedMessage, PeerId, RequestMessage};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
@@ -58,9 +57,6 @@ lazy_static! {
pub static ref ADMIN_API_TOKEN: Option<String> = std::env::var("ZED_ADMIN_API_TOKEN")
.ok()
.and_then(|s| if s.is_empty() { None } else { Some(s) });
- pub static ref ZED_APP_VERSION: Option<SemanticVersion> = std::env::var("ZED_APP_VERSION")
- .ok()
- .and_then(|v| v.parse().ok());
pub static ref ZED_APP_PATH: Option<PathBuf> =
std::env::var("ZED_APP_PATH").ok().map(PathBuf::from);
pub static ref ZED_ALWAYS_ACTIVE: bool =
@@ -1011,13 +1007,22 @@ impl Client {
.update(|cx| ReleaseChannel::try_global(cx))
.ok()
.flatten();
+ let app_version = cx
+ .update(|cx| AppVersion::global(cx).to_string())
+ .ok()
+ .unwrap_or_default();
let request = Request::builder()
.header(
"Authorization",
format!("{} {}", credentials.user_id, credentials.access_token),
)
- .header("x-zed-protocol-version", rpc::PROTOCOL_VERSION);
+ .header("x-zed-protocol-version", rpc::PROTOCOL_VERSION)
+ .header("x-zed-app-version", app_version)
+ .header(
+ "x-zed-release-channel",
+ release_channel.map(|r| r.dev_name()).unwrap_or("unknown"),
+ );
let http = self.http.clone();
cx.background_executor().spawn(async move {
@@ -61,6 +61,7 @@ util = { path = "../util" }
uuid.workspace = true
[dev-dependencies]
+release_channel = { path = "../release_channel" }
async-trait.workspace = true
audio = { path = "../audio" }
call = { path = "../call", features = ["test-support"] }
@@ -64,6 +64,7 @@ use time::OffsetDateTime;
use tokio::sync::{watch, Semaphore};
use tower::ServiceBuilder;
use tracing::{field, info_span, instrument, Instrument};
+use util::SemanticVersion;
pub const RECONNECT_TIMEOUT: Duration = Duration::from_secs(30);
pub const CLEANUP_TIMEOUT: Duration = Duration::from_secs(10);
@@ -795,6 +796,7 @@ fn broadcast<F>(
lazy_static! {
static ref ZED_PROTOCOL_VERSION: HeaderName = HeaderName::from_static("x-zed-protocol-version");
+ static ref ZED_APP_VERSION: HeaderName = HeaderName::from_static("x-zed-app-version");
}
pub struct ProtocolVersion(u32);
@@ -824,6 +826,32 @@ impl Header for ProtocolVersion {
}
}
+pub struct AppVersionHeader(SemanticVersion);
+impl Header for AppVersionHeader {
+ fn name() -> &'static HeaderName {
+ &ZED_APP_VERSION
+ }
+
+ fn decode<'i, I>(values: &mut I) -> Result<Self, axum::headers::Error>
+ where
+ Self: Sized,
+ I: Iterator<Item = &'i axum::http::HeaderValue>,
+ {
+ let version = values
+ .next()
+ .ok_or_else(axum::headers::Error::invalid)?
+ .to_str()
+ .map_err(|_| axum::headers::Error::invalid())?
+ .parse()
+ .map_err(|_| axum::headers::Error::invalid())?;
+ Ok(Self(version))
+ }
+
+ fn encode<E: Extend<axum::http::HeaderValue>>(&self, values: &mut E) {
+ values.extend([self.0.to_string().parse().unwrap()]);
+ }
+}
+
pub fn routes(server: Arc<Server>) -> Router<Body> {
Router::new()
.route("/rpc", get(handle_websocket_request))
@@ -838,6 +866,7 @@ pub fn routes(server: Arc<Server>) -> Router<Body> {
pub async fn handle_websocket_request(
TypedHeader(ProtocolVersion(protocol_version)): TypedHeader<ProtocolVersion>,
+ _app_version_header: Option<TypedHeader<AppVersionHeader>>,
ConnectInfo(socket_address): ConnectInfo<SocketAddr>,
Extension(server): Extension<Arc<Server>>,
Extension(user): Extension<User>,
@@ -851,6 +880,7 @@ pub async fn handle_websocket_request(
)
.into_response();
}
+
let socket_address = socket_address.to_string();
ws.on_upgrade(move |socket| {
use util::ResultExt;
@@ -153,6 +153,7 @@ impl TestServer {
}
let settings = SettingsStore::test(cx);
cx.set_global(settings);
+ release_channel::init("0.0.0", cx);
client::init_settings(cx);
});
@@ -1,14 +1,13 @@
-use client::ZED_APP_VERSION;
use gpui::AppContext;
use human_bytes::human_bytes;
-use release_channel::ReleaseChannel;
+use release_channel::{AppVersion, ReleaseChannel};
use serde::Serialize;
use std::{env, fmt::Display};
use sysinfo::{RefreshKind, System, SystemExt};
#[derive(Clone, Debug, Serialize)]
pub struct SystemSpecs {
- app_version: Option<String>,
+ app_version: String,
release_channel: &'static str,
os_name: &'static str,
os_version: Option<String>,
@@ -18,9 +17,7 @@ pub struct SystemSpecs {
impl SystemSpecs {
pub fn new(cx: &AppContext) -> Self {
- let app_version = ZED_APP_VERSION
- .or_else(|| cx.app_metadata().app_version)
- .map(|v| v.to_string());
+ let app_version = AppVersion::global(cx).to_string();
let release_channel = ReleaseChannel::global(cx).display_name();
let os_name = cx.app_metadata().os_name;
let system = System::new_with_specifics(RefreshKind::new().with_memory());
@@ -48,18 +45,15 @@ impl Display for SystemSpecs {
Some(os_version) => format!("OS: {} {}", self.os_name, os_version),
None => format!("OS: {}", self.os_name),
};
- let app_version_information = self
- .app_version
- .as_ref()
- .map(|app_version| format!("Zed: v{} ({})", app_version, self.release_channel));
+ let app_version_information =
+ format!("Zed: v{} ({})", self.app_version, self.release_channel);
let system_specs = [
app_version_information,
- Some(os_information),
- Some(format!("Memory: {}", human_bytes(self.memory as f64))),
- Some(format!("Architecture: {}", self.architecture)),
+ os_information,
+ format!("Memory: {}", human_bytes(self.memory as f64)),
+ format!("Architecture: {}", self.architecture),
]
.into_iter()
- .flatten()
.collect::<Vec<String>>()
.join("\n");
@@ -11,7 +11,7 @@ use crate::{
Pixels, PlatformInput, Point, RenderGlyphParams, RenderImageParams, RenderSvgParams, Scene,
SharedString, Size, Task, TaskLabel, WindowContext,
};
-use anyhow::{anyhow, Result};
+use anyhow::Result;
use async_task::Runnable;
use futures::channel::oneshot;
use parking::Unparker;
@@ -23,11 +23,10 @@ use std::hash::{Hash, Hasher};
use std::time::Duration;
use std::{
any::Any,
- fmt::{self, Debug, Display},
+ fmt::{self, Debug},
ops::Range,
path::{Path, PathBuf},
rc::Rc,
- str::FromStr,
sync::Arc,
};
use uuid::Uuid;
@@ -39,6 +38,7 @@ pub(crate) use mac::*;
#[cfg(any(test, feature = "test-support"))]
pub(crate) use test::*;
use time::UtcOffset;
+pub use util::SemanticVersion;
#[cfg(target_os = "macos")]
pub(crate) fn current_platform() -> Rc<dyn Platform> {
@@ -697,45 +697,6 @@ impl Default for CursorStyle {
}
}
-/// A datastructure representing a semantic version number
-#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
-pub struct SemanticVersion {
- major: usize,
- minor: usize,
- patch: usize,
-}
-
-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()
- .ok_or_else(|| anyhow!("missing major version number"))?
- .parse()?;
- let minor = components
- .next()
- .ok_or_else(|| anyhow!("missing minor version number"))?
- .parse()?;
- let patch = components
- .next()
- .ok_or_else(|| anyhow!("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)
- }
-}
-
/// A clipboard item that should be copied to the clipboard
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct ClipboardItem {
@@ -1,9 +1,9 @@
-use gpui::{AppContext, Global};
+use gpui::{AppContext, Global, SemanticVersion};
use once_cell::sync::Lazy;
use std::env;
#[doc(hidden)]
-pub static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
+static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
Lazy::new(|| {
env::var("ZED_RELEASE_CHANNEL")
.unwrap_or_else(|_| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string())
@@ -11,6 +11,7 @@ pub static RELEASE_CHANNEL_NAME: Lazy<String> = if cfg!(debug_assertions) {
} else {
Lazy::new(|| include_str!("../../zed/RELEASE_CHANNEL").trim().to_string())
};
+
#[doc(hidden)]
pub static RELEASE_CHANNEL: Lazy<ReleaseChannel> =
Lazy::new(|| match RELEASE_CHANNEL_NAME.as_str() {
@@ -39,6 +40,29 @@ impl AppCommitSha {
}
}
+struct GlobalAppVersion(SemanticVersion);
+
+impl Global for GlobalAppVersion {}
+
+pub struct AppVersion;
+
+impl AppVersion {
+ pub fn init(pkg_version: &str, cx: &mut AppContext) {
+ let version = if let Some(from_env) = env::var("ZED_APP_VERSION").ok() {
+ from_env.parse().expect("invalid ZED_APP_VERSION")
+ } else {
+ cx.app_metadata()
+ .app_version
+ .unwrap_or_else(|| pkg_version.parse().expect("invalid version in Cargo.toml"))
+ };
+ cx.set_global(GlobalAppVersion(version))
+ }
+
+ pub fn global(cx: &AppContext) -> SemanticVersion {
+ cx.global::<GlobalAppVersion>().0
+ }
+}
+
#[derive(Debug, Copy, Clone, PartialEq, Eq, Default)]
pub enum ReleaseChannel {
#[default]
@@ -52,11 +76,12 @@ struct GlobalReleaseChannel(ReleaseChannel);
impl Global for GlobalReleaseChannel {}
-impl ReleaseChannel {
- pub fn init(cx: &mut AppContext) {
- cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL))
- }
+pub fn init(pkg_version: &str, cx: &mut AppContext) {
+ AppVersion::init(pkg_version, cx);
+ cx.set_global(GlobalReleaseChannel(*RELEASE_CHANNEL))
+}
+impl ReleaseChannel {
pub fn global(cx: &AppContext) -> Self {
cx.global::<GlobalReleaseChannel>().0
}
@@ -210,7 +210,7 @@ impl SettingsStore {
if let Some(release_settings) = &self
.raw_user_settings
- .get(&*release_channel::RELEASE_CHANNEL_NAME)
+ .get(&*release_channel::RELEASE_CHANNEL.dev_name())
{
if let Some(release_settings) = setting_value
.deserialize_setting(&release_settings)
@@ -543,7 +543,7 @@ impl SettingsStore {
if let Some(release_settings) = &self
.raw_user_settings
- .get(&*release_channel::RELEASE_CHANNEL_NAME)
+ .get(&*release_channel::RELEASE_CHANNEL.dev_name())
{
if let Some(release_settings) = setting_value
.deserialize_setting(&release_settings)
@@ -0,0 +1,46 @@
+use std::{
+ fmt::{self, Display},
+ str::FromStr,
+};
+
+use anyhow::{anyhow, Result};
+use serde::Serialize;
+
+/// A datastructure representing a semantic version number
+#[derive(Clone, Copy, Debug, Default, Eq, Ord, PartialEq, PartialOrd, Serialize)]
+pub struct SemanticVersion {
+ pub major: usize,
+ pub minor: usize,
+ pub patch: usize,
+}
+
+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()
+ .ok_or_else(|| anyhow!("missing major version number"))?
+ .parse()?;
+ let minor = components
+ .next()
+ .ok_or_else(|| anyhow!("missing minor version number"))?
+ .parse()?;
+ let patch = components
+ .next()
+ .ok_or_else(|| anyhow!("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)
+ }
+}
@@ -3,6 +3,7 @@ pub mod fs;
pub mod github;
pub mod http;
pub mod paths;
+mod semantic_version;
#[cfg(any(test, feature = "test-support"))]
pub mod test;
@@ -10,6 +11,7 @@ pub use backtrace::Backtrace;
use futures::Future;
use lazy_static::lazy_static;
use rand::{seq::SliceRandom, Rng};
+pub use semantic_version::SemanticVersion;
use std::{
borrow::Cow,
cmp::{self, Ordering},
@@ -120,7 +120,7 @@ fn main() {
});
app.run(move |cx| {
- ReleaseChannel::init(cx);
+ release_channel::init(env!("CARGO_PKG_VERSION"), cx);
if let Some(build_sha) = option_env!("ZED_COMMIT_SHA") {
AppCommitSha::set_global(AppCommitSha(build_sha.into()), cx);
}
@@ -608,9 +608,13 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
std::process::exit(-1);
}
- let app_version = client::ZED_APP_VERSION
- .or(app_metadata.app_version)
- .map_or("dev".to_string(), |v| v.to_string());
+ let app_version = if let Some(version) = app_metadata.app_version {
+ version.to_string()
+ } else {
+ option_env!("CARGO_PKG_VERSION")
+ .unwrap_or("dev")
+ .to_string()
+ };
let backtrace = Backtrace::new();
let mut backtrace = backtrace
@@ -639,7 +643,7 @@ fn init_panic_hook(app: &App, installation_id: Option<String>, session_id: Strin
file: location.file().into(),
line: location.line(),
}),
- app_version: app_version.clone(),
+ app_version: app_version.to_string(),
release_channel: RELEASE_CHANNEL.display_name().into(),
os_name: app_metadata.os_name.into(),
os_version: app_metadata