Skip to content

Commit 6ea5617

Browse files
Jesús Lapastoraellipsis-dev[bot]
andauthored
Regression: Emit notification to check CLI version against client (#2404)
The notification for the check is restored; now a notification is sent regardless of mismatches between LSP & generators. Will not send a notification if generators don't match, since then we don't have a baseline to compare to. Speculative downloading doesn't make a lot of sense either: download LSP for previous version -> user changes version to match highest version -> download missed. Since # of generators is small then probability of failure is pretty close to 50%. <!-- ELLIPSIS_HIDDEN --> ---- > [!IMPORTANT] > Restores CLI version check notification and refactors version checking logic for robustness. > > - **Behavior**: > - Restores notification for CLI version check against client, sent regardless of LSP & generator mismatches. > - Skips notification if generators don't match, lacking a baseline for comparison. > - **Functions**: > - Refactors `get_common_generator_version()` in `mod.rs` to centralize version checking logic. > - Introduces `send_generator_version()` in `did_save_text_document.rs` to handle version notifications. > - **Diagnostics**: > - Updates `project_diagnostics()` in `diagnostics.rs` to handle generator version mismatches. > - **Misc**: > - Removes speculative downloading logic. > > <sup>This description was created by </sup>[<img alt="Ellipsis" src="https://img.shields.io/badge/Ellipsis-blue?color=175173">](https://www.ellipsis.dev?ref=BoundaryML%2Fbaml&utm_source=github&utm_medium=referral)<sup> for e0a91d9. You can [customize](https://app.ellipsis.dev/BoundaryML/settings/summaries) this summary. It will automatically update as commits are pushed.</sup> <!-- ELLIPSIS_HIDDEN --> --------- Co-authored-by: ellipsis-dev[bot] <65095814+ellipsis-dev[bot]@users.noreply.github.com>
1 parent 992acaa commit 6ea5617

File tree

4 files changed

+126
-101
lines changed

4 files changed

+126
-101
lines changed

engine/language_server/src/baml_project/mod.rs

Lines changed: 61 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -1255,84 +1255,80 @@ impl Project {
12551255
/// Checks if all generators use the same major.minor version.
12561256
/// Returns Ok(()) if they do,
12571257
/// otherwise returns an Err with a descriptive message.
1258-
pub fn get_common_generator_version(
1259-
&self,
1260-
feature_flags: &[String],
1261-
client_version: Option<&str>,
1262-
) -> anyhow::Result<String> {
1263-
let runtime_version = env!("CARGO_PKG_VERSION");
1264-
1258+
pub fn get_common_generator_version(&self) -> anyhow::Result<String> {
12651259
// list generators. If we can't get the runtime, we'll error out.
12661260
let generators = self
12671261
.runtime()?
12681262
.codegen_generators()
12691263
.map(|gen| gen.version.as_str());
12701264

1271-
// add runtime version on top since that's what we want to compare with.
1272-
let gen_version_strings = [runtime_version]
1273-
.into_iter()
1274-
.chain(client_version)
1275-
.chain(generators);
1276-
1277-
let mut major_minor_versions = std::collections::HashMap::new();
1278-
let mut highest_patch_by_major_minor = std::collections::HashMap::new();
1279-
1280-
// Track major.minor versions and find highest patch for each
1281-
for version_str in gen_version_strings {
1282-
if let Ok(version) = semver::Version::parse(version_str) {
1283-
let major_minor = format!("{}.{}", version.major, version.minor);
1284-
1285-
// Track generators with this major.minor
1286-
major_minor_versions
1287-
.entry(major_minor.clone())
1288-
.or_insert_with(Vec::new)
1289-
.push(version_str);
1290-
1291-
// Track highest patch version for this major.minor
1292-
highest_patch_by_major_minor
1293-
.entry(major_minor)
1294-
.and_modify(|highest_patch: &mut u64| {
1295-
if version.patch > *highest_patch {
1296-
*highest_patch = version.patch;
1297-
}
1298-
})
1299-
.or_insert(version.patch);
1300-
} else {
1301-
tracing::warn!("Invalid semver version in generator: {}", version_str);
1302-
// Consider how to handle invalid versions - for now, we ignore them for the check
1303-
}
1265+
common_version_up_to_patch(generators)
1266+
}
1267+
}
1268+
1269+
/// Given a set of SemVer version strings, match them to the same `major.minor`, returning an error otherwise. Invalid semver strings are ignored for the check.
1270+
/// an error otherwise.
1271+
pub fn common_version_up_to_patch<'a>(
1272+
gen_version_strings: impl IntoIterator<Item = &'a str>,
1273+
) -> anyhow::Result<String> {
1274+
let mut major_minor_versions = std::collections::HashMap::new();
1275+
let mut highest_patch_by_major_minor = std::collections::HashMap::new();
1276+
1277+
// Track major.minor versions and find highest patch for each
1278+
for version_str in gen_version_strings {
1279+
if let Ok(version) = semver::Version::parse(version_str) {
1280+
let major_minor = format!("{}.{}", version.major, version.minor);
1281+
1282+
// Track generators with this major.minor
1283+
major_minor_versions
1284+
.entry(major_minor.clone())
1285+
.or_insert_with(Vec::new)
1286+
.push(version_str);
1287+
1288+
// Track highest patch version for this major.minor
1289+
highest_patch_by_major_minor
1290+
.entry(major_minor)
1291+
.and_modify(|highest_patch: &mut u64| {
1292+
if version.patch > *highest_patch {
1293+
*highest_patch = version.patch;
1294+
}
1295+
})
1296+
.or_insert(version.patch);
1297+
} else {
1298+
tracing::warn!("Invalid semver version in generator: {}", version_str);
1299+
// Consider how to handle invalid versions - for now, we ignore them for the check
13041300
}
1301+
}
13051302

1306-
// If there's more than one major.minor version, return an error
1307-
if major_minor_versions.len() > 1 {
1308-
let versions_str = major_minor_versions
1309-
.keys()
1310-
.map(|v| format!("'{v}'"))
1311-
.collect::<Vec<_>>()
1312-
.join(", ");
1303+
// If there's more than one major.minor version, return an error
1304+
if major_minor_versions.len() > 1 {
1305+
let versions_str = major_minor_versions
1306+
.keys()
1307+
.map(|v| format!("'{v}'"))
1308+
.collect::<Vec<_>>()
1309+
.join(", ");
13131310

1314-
let message = anyhow::anyhow!(
1315-
"Multiple generator major.minor versions detected: {versions_str}. Major and minor versions must match across all generators."
1316-
);
1317-
Err(message)
1318-
// If there's only one major.minor version, return it with the highest patch
1319-
} else if let Some((version, _)) = major_minor_versions.iter().next() {
1320-
if let Some(highest_patch) = highest_patch_by_major_minor.get(version) {
1321-
// Parse the version string to create a proper semver::Version
1322-
if let Ok(mut v) = Version::parse(&format!("{version}.0")) {
1323-
// Update with the highest patch version
1324-
v.patch = *highest_patch;
1325-
Ok(v.to_string())
1326-
} else {
1327-
Ok(format!("{version}.{highest_patch}"))
1328-
}
1311+
let message = anyhow::anyhow!(
1312+
"Multiple major.minor versions detected: {versions_str}. Major and minor versions must match across all generators."
1313+
);
1314+
Err(message)
1315+
// If there's only one major.minor version, return it with the highest patch
1316+
} else if let Some((version, _)) = major_minor_versions.into_iter().next() {
1317+
if let Some(highest_patch) = highest_patch_by_major_minor.get(&version) {
1318+
// Parse the version string to create a proper semver::Version
1319+
if let Ok(mut v) = Version::parse(&format!("{version}.0")) {
1320+
// Update with the highest patch version
1321+
v.patch = *highest_patch;
1322+
Ok(v.to_string())
13291323
} else {
1330-
Ok(version.clone())
1324+
Ok(format!("{version}.{highest_patch}"))
13311325
}
1332-
// Fallback to the runtime version if no valid versions were found
13331326
} else {
1334-
Err(anyhow::anyhow!("No valid generator versions found"))
1327+
Ok(version)
13351328
}
1329+
// Fallback to the runtime version if no valid versions were found
1330+
} else {
1331+
Err(anyhow::anyhow!("No valid generator versions found"))
13361332
}
13371333
}
13381334

engine/language_server/src/server/api/diagnostics.rs

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -268,9 +268,7 @@ pub fn project_diagnostics(
268268
}
269269

270270
// Check for generator version mismatch as well.
271-
if let Err(message) = guard
272-
.get_common_generator_version(feature_flags, session.baml_settings.get_client_version())
273-
{
271+
if let Err(message) = guard.get_common_generator_version() {
274272
// Add the diagnostic to all generators
275273
if let Ok(generators) = guard.list_generators(feature_flags) {
276274
// Need to list generators again to get their spans

engine/language_server/src/server/api/notifications/did_open.rs

Lines changed: 7 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,10 @@ use crate::{
77
server::{
88
api::{
99
diagnostics::publish_session_lsp_diagnostics,
10-
notifications::baml_src_version::BamlSrcVersionPayload,
10+
notifications::{
11+
baml_src_version::BamlSrcVersionPayload,
12+
did_save_text_document::send_generator_version,
13+
},
1114
traits::{NotificationHandler, SyncNotificationHandler},
1215
ResultExt,
1316
},
@@ -78,22 +81,9 @@ impl SyncNotificationHandler for DidOpenTextDocumentHandler {
7881
.as_ref()
7982
.unwrap_or(&default_flags);
8083
let client_version = session.baml_settings.get_client_version();
81-
if let Ok(version) =
82-
locked.get_common_generator_version(effective_flags, client_version)
83-
{
84-
notifier
85-
.0
86-
.send(lsp_server::Message::Notification(
87-
lsp_server::Notification::new(
88-
"baml_src_generator_version".to_string(),
89-
BamlSrcVersionPayload {
90-
version,
91-
root_path: locked.root_path().to_string_lossy().to_string(),
92-
},
93-
),
94-
))
95-
.internal_error()?;
96-
}
84+
85+
let generator_version = locked.get_common_generator_version();
86+
send_generator_version(&notifier, &locked, generator_version.as_ref().ok());
9787
} else {
9888
tracing::error!("Failed to get or create project for path: {:?}", file_path);
9989
show_err_msg!("Failed to get or create project for path: {:?}", file_path);

engine/language_server/src/server/api/notifications/did_save_text_document.rs

Lines changed: 57 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use std::borrow::Cow;
33
use lsp_types::{self as types, notification as notif, request::Request, ConfigurationParams};
44

55
use crate::{
6+
baml_project::{common_version_up_to_patch, Project},
67
server::{
78
api::{self, notifications::baml_src_version::BamlSrcVersionPayload, ResultExt},
89
client::{Notifier, Requester},
@@ -57,22 +58,41 @@ impl super::SyncNotificationHandler for DidSaveTextDocument {
5758
.unwrap_or(&default_flags);
5859
let client_version = session.baml_settings.get_client_version();
5960

60-
let version = locked
61-
.get_common_generator_version(effective_flags, client_version)
62-
.map_err(|msg| api::Error {
63-
error: anyhow::anyhow!(msg),
64-
code: lsp_server::ErrorCode::InternalError,
65-
})?;
66-
67-
let _ = notifier.0.send(lsp_server::Message::Notification(
68-
lsp_server::Notification::new(
69-
"baml_src_generator_version".to_string(),
70-
BamlSrcVersionPayload {
71-
version,
72-
root_path: locked.root_path().to_string_lossy().to_string(),
73-
},
74-
),
75-
));
61+
// There are 3 components to check version of:
62+
// - generators -> if they don't resolve to the same major/minor, then we'll error for now.
63+
// - LSP client (vscode extension)
64+
// - LSP server (CLI binary)
65+
//
66+
// Upon baml_src_generator_version notification, LSP client will replace the server version
67+
// with the given version.
68+
// If there's no generation version to be used, the notification won't be sent.
69+
//
70+
// Independently, the three versions will be checked against each other. If a major.minor
71+
// version can't be reached, then nothing is going to be generated.
72+
73+
let generator_version = locked.get_common_generator_version();
74+
75+
let opt_version = generator_version.as_ref().ok();
76+
send_generator_version(&notifier, &locked, opt_version);
77+
78+
// Make sure to check all available versions againt each other, & generate only if there's
79+
// no errors.
80+
81+
{
82+
let gen_version_iter = generator_version.as_ref().map(AsRef::as_ref);
83+
84+
let runtime_version = env!("CARGO_PKG_VERSION");
85+
let version_iter = [runtime_version]
86+
.into_iter()
87+
.chain(client_version)
88+
.chain(gen_version_iter);
89+
90+
// check all versions against each other, ignoring any errors
91+
// in common generator version.
92+
_ = common_version_up_to_patch(version_iter).internal_error()?;
93+
// Make sure to propagate the generator version check as well.
94+
_ = generator_version.internal_error()?;
95+
}
7696

7797
let default_flags2 = vec!["beta".to_string()];
7898
let effective_flags = session
@@ -96,6 +116,27 @@ impl super::SyncNotificationHandler for DidSaveTextDocument {
96116
}
97117
}
98118

119+
/// Upon `baml_src_generator_version` notification, LSP client will replace the server version
120+
/// with the given version.
121+
/// If there's no generation version to be used, the notification won't be sent.
122+
pub(crate) fn send_generator_version(
123+
notifier: &Notifier,
124+
project: &Project,
125+
opt_version: Option<&impl ToOwned<Owned = String>>,
126+
) {
127+
if let Some(version) = opt_version.map(ToOwned::to_owned) {
128+
let _ = notifier.0.send(lsp_server::Message::Notification(
129+
lsp_server::Notification::new(
130+
"baml_src_generator_version".to_string(),
131+
BamlSrcVersionPayload {
132+
version,
133+
root_path: project.root_path().to_string_lossy().to_string(),
134+
},
135+
),
136+
));
137+
}
138+
}
139+
99140
// Do not use this yet, it seems it has an outdated view of the project files and it generates
100141
// stale baml clients
101142
impl super::BackgroundDocumentNotificationHandler for DidSaveTextDocument {

0 commit comments

Comments
 (0)