diff --git a/crates/pet/build.rs b/crates/pet/build.rs index 240d2fca..bf8bd6a8 100644 --- a/crates/pet/build.rs +++ b/crates/pet/build.rs @@ -2,6 +2,17 @@ // Licensed under the MIT License. fn main() { + println!("cargo:rerun-if-env-changed=PET_BUILD_ID"); + println!("cargo:rerun-if-env-changed=BUILD_BUILDID"); + + if let Some(build_id) = std::env::var("PET_BUILD_ID") + .ok() + .or_else(|| std::env::var("BUILD_BUILDID").ok()) + .filter(|value| !value.is_empty()) + { + println!("cargo:rustc-env=PET_BUILD_ID={build_id}"); + } + #[cfg(target_os = "windows")] { if std::env::var("CARGO_BIN_NAME").is_err() { diff --git a/crates/pet/src/jsonrpc.rs b/crates/pet/src/jsonrpc.rs index 9ae5478c..8ff6c2c2 100644 --- a/crates/pet/src/jsonrpc.rs +++ b/crates/pet/src/jsonrpc.rs @@ -507,6 +507,7 @@ pub fn start_jsonrpc_server() { }; let mut handlers = HandlersKeyedByMethodName::new(Arc::new(context)); + handlers.add_request_handler("info", handle_info); handlers.add_request_handler("configure", handle_configure); handlers.add_request_handler("refresh", handle_refresh); handlers.add_request_handler("resolve", handle_resolve); @@ -516,6 +517,29 @@ pub fn start_jsonrpc_server() { start_server(&handlers) } +#[derive(Debug, Clone, Deserialize, Serialize)] +#[serde(rename_all = "camelCase")] +pub struct InfoResponse { + pub pet_version: String, + #[serde(skip_serializing_if = "Option::is_none")] + pub build_id: Option, +} + +impl InfoResponse { + fn current() -> Self { + Self { + pet_version: env!("CARGO_PKG_VERSION").to_string(), + build_id: option_env!("PET_BUILD_ID") + .filter(|value| !value.is_empty()) + .map(ToString::to_string), + } + } +} + +pub fn handle_info(_context: Arc, id: u32, _params: Value) { + send_reply(id, Some(InfoResponse::current())); +} + #[derive(Debug, Clone, Deserialize, Serialize)] #[serde(rename_all = "camelCase")] pub struct ConfigureOptions { @@ -1512,6 +1536,17 @@ mod tests { ); } + #[test] + fn test_info_response_uses_package_version_and_optional_build_id() { + let info = InfoResponse::current(); + + assert_eq!(info.pet_version, env!("CARGO_PKG_VERSION")); + assert!(info + .build_id + .as_deref() + .is_none_or(|build_id| !build_id.is_empty())); + } + #[test] fn test_parse_refresh_options_rejects_non_empty_array() { assert!(parse_refresh_options(json!([{"searchKind": "Conda"}])).is_err()); diff --git a/crates/pet/tests/jsonrpc_client.rs b/crates/pet/tests/jsonrpc_client.rs index 86fcad01..a10270b4 100644 --- a/crates/pet/tests/jsonrpc_client.rs +++ b/crates/pet/tests/jsonrpc_client.rs @@ -21,6 +21,13 @@ pub struct RefreshResult { pub duration: u128, } +#[derive(Debug, Clone, Deserialize, PartialEq, Eq)] +#[serde(rename_all = "camelCase")] +pub struct PetInfoResponse { + pub pet_version: String, + pub build_id: Option, +} + #[derive(Debug, Clone, Deserialize, PartialEq, Eq)] #[serde(rename_all = "camelCase")] pub struct EnvironmentNotification { @@ -156,6 +163,10 @@ impl PetJsonRpcClient { ) } + pub fn info(&self) -> Result { + self.send_request("info", json!({}), DEFAULT_REQUEST_TIMEOUT) + } + #[allow(dead_code)] pub fn resolve(&self, executable: &str) -> Result { self.send_request_value( diff --git a/crates/pet/tests/jsonrpc_server_test.rs b/crates/pet/tests/jsonrpc_server_test.rs index f80b6098..4e16ed5c 100644 --- a/crates/pet/tests/jsonrpc_server_test.rs +++ b/crates/pet/tests/jsonrpc_server_test.rs @@ -110,6 +110,19 @@ fn assert_single_environment( assert_eq!(environment.error, None); } +#[test] +fn info_reports_pet_version_and_optional_build_id() { + let client = PetJsonRpcClient::spawn().expect("failed to spawn PET server"); + + let info = client.info().expect("info request failed"); + + assert_eq!(info.pet_version, env!("CARGO_PKG_VERSION")); + assert!(info + .build_id + .as_deref() + .is_none_or(|build_id| !build_id.is_empty())); +} + #[test] fn configure_and_workspace_refresh_report_fake_venv() { let client = PetJsonRpcClient::spawn().expect("failed to spawn PET server"); diff --git a/docs/JSONRPC.md b/docs/JSONRPC.md index b387e5a6..a2388833 100644 --- a/docs/JSONRPC.md +++ b/docs/JSONRPC.md @@ -11,6 +11,35 @@ For samples using JSONRPC, please have a look at the [sample.js](./sample.js) fi Any requests/notifications not documented here are not supported. +# Info Request + +Returns metadata about the running PET binary. This request does not require a prior +`configure` request. Clients can cache this response and attach it to PET-related +telemetry such as `refresh` and `resolve` timings. + +_Request_: + +- method: `info` +- params: `{}` + +_Response_: + +- result: `InfoResponse` defined as below. + +```typescript +interface InfoResponse { + /** + * PET package version baked into the binary at build time. + * Pre-release builds may include a suffix such as `0.1.0-dev.12345`. + */ + petVersion: string; + /** + * Build identifier baked into the binary when built by CI. + */ + buildId?: string; +} +``` + # Configuration Request This should always be the first request sent to the tool.