From 18ca0e0edfb742444ee8d581d91e9e8b76ac8a03 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:21:15 +0000 Subject: [PATCH 1/4] Initial plan From fdbceff2d206231e84db9528432425817fd79c69 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:25:49 +0000 Subject: [PATCH 2/4] Add debugging documentation to README Co-authored-by: anthonykim1 <62267334+anthonykim1@users.noreply.github.com> --- README.md | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) diff --git a/README.md b/README.md index 64ca2e3e..65b9203d 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,85 @@ This project will be consumed by the [Python extension](https://marketplace.visu Our approach prioritizes performance and efficiency by leveraging Rust. We minimize I/O operations by collecting all necessary environment information at once, which reduces repeated I/O and the need to spawn additional processes, significantly enhancing overall performance. +## Debugging Python Environment Issues + +If you're experiencing issues with Python interpreter detection in VS Code (such as the Run button not working, Python not being recognized, or interpreters not persisting), you can use PET to diagnose the problem. + +### Running PET for Debugging + +PET can be run directly from the command line to discover all Python environments on your system. This helps identify whether the issue is with environment discovery or elsewhere. + +#### Quick Start + +1. **Download or build PET**: + - Download pre-built binaries from the [releases page](https://github.com/microsoft/python-environment-tools/releases) + - Or build from source: `cargo build --release` + +2. **Run PET to find all environments**: + ```bash + # On Linux/macOS + ./pet find --list --verbose + + # On Windows + pet.exe find --list --verbose + ``` + +3. **Share the output** with maintainers when reporting issues + +#### Common Commands + +- **Find all Python environments** (default behavior): + ```bash + pet + ``` + +- **Find all environments with detailed logging**: + ```bash + pet find --list --verbose + ``` + +- **Search only in workspace/project directories**: + ```bash + pet find --list --workspace + ``` + +- **Search for a specific environment type** (e.g., Conda): + ```bash + pet find --list --kind conda + ``` + +- **Resolve a specific Python executable**: + ```bash + pet resolve /path/to/python + ``` + +#### Understanding the Output + +The output includes: + +- **Discovered Environments**: List of Python installations found, including: + - Type (Conda, Venv, System Python, etc.) + - Executable path + - Version + - Prefix (sys.prefix) + - Architecture (x64/x86) + - Symlinks + +- **Timing Information**: How long each locator took to search + +- **Summary Statistics**: Count of environments by type + +#### Reporting Issues + +When reporting Python detection issues, please include: + +1. The full output from running `pet find --list --verbose` +2. Your operating system and version +3. VS Code and Python extension versions +4. Description of the issue + +This information helps maintainers diagnose whether the problem is with PET's discovery logic or elsewhere in the VS Code Python extension. + ## Contributing This project welcomes contributions and suggestions. Most contributions require you to agree to a From b9210a963d5c269460df8e1044198c77e055e052 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:28:39 +0000 Subject: [PATCH 3/4] Add JSON output support to PET Co-authored-by: anthonykim1 <62267334+anthonykim1@users.noreply.github.com> --- README.md | 5 + crates/pet-reporter/src/json.rs | 64 +++++++++++++ crates/pet-reporter/src/lib.rs | 1 + crates/pet/src/lib.rs | 162 ++++++++++++++++++-------------- crates/pet/src/main.rs | 7 ++ 5 files changed, 170 insertions(+), 69 deletions(-) create mode 100644 crates/pet-reporter/src/json.rs diff --git a/README.md b/README.md index 65b9203d..94ca47db 100644 --- a/README.md +++ b/README.md @@ -68,6 +68,11 @@ PET can be run directly from the command line to discover all Python environment pet find --list --verbose ``` +- **Find all environments and output as JSON**: + ```bash + pet find --json + ``` + - **Search only in workspace/project directories**: ```bash pet find --list --workspace diff --git a/crates/pet-reporter/src/json.rs b/crates/pet-reporter/src/json.rs new file mode 100644 index 00000000..63ccb6ea --- /dev/null +++ b/crates/pet-reporter/src/json.rs @@ -0,0 +1,64 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use pet_core::{ + manager::EnvManager, python_environment::PythonEnvironment, reporter::Reporter, + telemetry::TelemetryEvent, +}; +use serde::Serialize; +use std::sync::{Arc, Mutex}; + +#[derive(Serialize)] +#[serde(rename_all = "camelCase")] +pub struct JsonOutput { + pub managers: Vec, + pub environments: Vec, +} + +/// Reporter that collects environments and managers for JSON output +pub struct JsonReporter { + managers: Arc>>, + environments: Arc>>, +} + +impl JsonReporter { + pub fn new() -> Self { + JsonReporter { + managers: Arc::new(Mutex::new(vec![])), + environments: Arc::new(Mutex::new(vec![])), + } + } + + pub fn output_json(&self) { + let managers = self.managers.lock().unwrap().clone(); + let environments = self.environments.lock().unwrap().clone(); + + let output = JsonOutput { + managers, + environments, + }; + + match serde_json::to_string_pretty(&output) { + Ok(json) => println!("{}", json), + Err(e) => eprintln!("Error serializing to JSON: {}", e), + } + } +} + +impl Reporter for JsonReporter { + fn report_telemetry(&self, _event: &TelemetryEvent) { + // No telemetry in JSON output + } + + fn report_manager(&self, manager: &EnvManager) { + self.managers.lock().unwrap().push(manager.clone()); + } + + fn report_environment(&self, env: &PythonEnvironment) { + self.environments.lock().unwrap().push(env.clone()); + } +} + +pub fn create_reporter() -> JsonReporter { + JsonReporter::new() +} diff --git a/crates/pet-reporter/src/lib.rs b/crates/pet-reporter/src/lib.rs index 82280be9..828209ad 100644 --- a/crates/pet-reporter/src/lib.rs +++ b/crates/pet-reporter/src/lib.rs @@ -4,5 +4,6 @@ pub mod cache; pub mod collect; pub mod environment; +pub mod json; pub mod jsonrpc; pub mod stdio; diff --git a/crates/pet/src/lib.rs b/crates/pet/src/lib.rs index 68f9aed4..0b163ca8 100644 --- a/crates/pet/src/lib.rs +++ b/crates/pet/src/lib.rs @@ -32,14 +32,18 @@ pub struct FindOptions { pub workspace_only: bool, pub cache_directory: Option, pub kind: Option, + pub json: bool, } pub fn find_and_report_envs_stdio(options: FindOptions) { - stdio::initialize_logger(if options.verbose { - log::LevelFilter::Trace - } else { - log::LevelFilter::Warn - }); + // Don't initialize logger if JSON output is requested to avoid polluting JSON + if !options.json { + stdio::initialize_logger(if options.verbose { + log::LevelFilter::Trace + } else { + log::LevelFilter::Warn + }); + } let now = SystemTime::now(); let config = create_config(&options); let search_scope = if options.workspace_only { @@ -70,9 +74,12 @@ pub fn find_and_report_envs_stdio(options: FindOptions) { search_scope, ); - println!("Completed in {}ms", now.elapsed().unwrap().as_millis()) + if !options.json { + println!("Completed in {}ms", now.elapsed().unwrap().as_millis()) + } } + fn create_config(options: &FindOptions) -> Configuration { let mut config = Configuration::default(); @@ -120,77 +127,94 @@ fn find_envs( Some(SearchScope::Global(kind)) => Some(kind), _ => None, }; - let stdio_reporter = Arc::new(stdio::create_reporter(options.print_list, kind)); - let reporter = CacheReporter::new(stdio_reporter.clone()); - let summary = find_and_report_envs(&reporter, config, locators, environment, search_scope); - if options.report_missing { - // By now all conda envs have been found - // Spawn conda - // & see if we can find more environments by spawning conda. - let _ = conda_locator.find_and_report_missing_envs(&reporter, None); - let _ = poetry_locator.find_and_report_missing_envs(&reporter, None); - } + if options.json { + // Use JSON reporter + let json_reporter = Arc::new(pet_reporter::json::create_reporter()); + let reporter = CacheReporter::new(json_reporter.clone()); - if options.print_summary { - let summary = summary.lock().unwrap(); - if !summary.locators.is_empty() { - println!(); - println!("Breakdown by each locator:"); - println!("--------------------------"); - for locator in summary.locators.iter() { - println!("{:<20} : {:?}", format!("{:?}", locator.0), locator.1); - } - println!() + let _ = find_and_report_envs(&reporter, config, locators, environment, search_scope); + if options.report_missing { + let _ = conda_locator.find_and_report_missing_envs(&reporter, None); + let _ = poetry_locator.find_and_report_missing_envs(&reporter, None); } - if !summary.breakdown.is_empty() { - println!("Breakdown for finding Environments:"); - println!("-----------------------------------"); - for item in summary.breakdown.iter() { - println!("{:<20} : {:?}", item.0, item.1); - } - println!(); + // Output JSON + json_reporter.output_json(); + } else { + // Use stdio reporter + let stdio_reporter = Arc::new(stdio::create_reporter(options.print_list, kind)); + let reporter = CacheReporter::new(stdio_reporter.clone()); + + let summary = find_and_report_envs(&reporter, config, locators, environment, search_scope); + if options.report_missing { + // By now all conda envs have been found + // Spawn conda + // & see if we can find more environments by spawning conda. + let _ = conda_locator.find_and_report_missing_envs(&reporter, None); + let _ = poetry_locator.find_and_report_missing_envs(&reporter, None); } - let summary = stdio_reporter.get_summary(); - if !summary.managers.is_empty() { - println!("Managers:"); - println!("---------"); - for (k, v) in summary - .managers - .clone() - .into_iter() - .map(|(k, v)| (format!("{k:?}"), v)) - .collect::>() - { - println!("{k:<20} : {v:?}"); + if options.print_summary { + let summary = summary.lock().unwrap(); + if !summary.locators.is_empty() { + println!(); + println!("Breakdown by each locator:"); + println!("--------------------------"); + for locator in summary.locators.iter() { + println!("{:<20} : {:?}", format!("{:?}", locator.0), locator.1); + } + println!() } - println!() - } - if !summary.environments.is_empty() { - let total = summary - .environments - .clone() - .iter() - .fold(0, |total, b| total + b.1); - println!("Environments ({total}):"); - println!("------------------"); - for (k, v) in summary - .environments - .clone() - .into_iter() - .map(|(k, v)| { - ( - k.map(|v| format!("{v:?}")).unwrap_or("Unknown".to_string()), - v, - ) - }) - .collect::>() - { - println!("{k:<20} : {v:?}"); + + if !summary.breakdown.is_empty() { + println!("Breakdown for finding Environments:"); + println!("-----------------------------------"); + for item in summary.breakdown.iter() { + println!("{:<20} : {:?}", item.0, item.1); + } + println!(); + } + + let summary = stdio_reporter.get_summary(); + if !summary.managers.is_empty() { + println!("Managers:"); + println!("---------"); + for (k, v) in summary + .managers + .clone() + .into_iter() + .map(|(k, v)| (format!("{k:?}"), v)) + .collect::>() + { + println!("{k:<20} : {v:?}"); + } + println!() + } + if !summary.environments.is_empty() { + let total = summary + .environments + .clone() + .iter() + .fold(0, |total, b| total + b.1); + println!("Environments ({total}):"); + println!("------------------"); + for (k, v) in summary + .environments + .clone() + .into_iter() + .map(|(k, v)| { + ( + k.map(|v| format!("{v:?}")).unwrap_or("Unknown".to_string()), + v, + ) + }) + .collect::>() + { + println!("{k:<20} : {v:?}"); + } + println!() } - println!() } } } diff --git a/crates/pet/src/main.rs b/crates/pet/src/main.rs index f22bf369..1d9fb5fd 100644 --- a/crates/pet/src/main.rs +++ b/crates/pet/src/main.rs @@ -53,6 +53,10 @@ enum Commands { /// Will not search in the workspace directories. #[arg(short, long, conflicts_with = "workspace")] kind: Option, + + /// Output results in JSON format. + #[arg(short, long)] + json: bool, }, /// Resolves & reports the details of the the environment to the standard output. Resolve { @@ -83,6 +87,7 @@ fn main() { workspace: false, cache_directory: None, kind: None, + json: false, }) { Commands::Find { list, @@ -92,6 +97,7 @@ fn main() { workspace, cache_directory, kind, + json, } => { let mut workspace_only = workspace; if search_paths.clone().is_some() @@ -113,6 +119,7 @@ fn main() { workspace_only, cache_directory, kind, + json, }); } Commands::Resolve { From 18d7bb1ba9d7354684aaa5ab7bba8ace741e16ff Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 29 Jan 2026 19:30:26 +0000 Subject: [PATCH 4/4] Address code review feedback Co-authored-by: anthonykim1 <62267334+anthonykim1@users.noreply.github.com> --- crates/pet-reporter/src/json.rs | 6 ++++++ crates/pet/src/main.rs | 1 + 2 files changed, 7 insertions(+) diff --git a/crates/pet-reporter/src/json.rs b/crates/pet-reporter/src/json.rs index 63ccb6ea..596a721d 100644 --- a/crates/pet-reporter/src/json.rs +++ b/crates/pet-reporter/src/json.rs @@ -21,6 +21,12 @@ pub struct JsonReporter { environments: Arc>>, } +impl Default for JsonReporter { + fn default() -> Self { + Self::new() + } +} + impl JsonReporter { pub fn new() -> Self { JsonReporter { diff --git a/crates/pet/src/main.rs b/crates/pet/src/main.rs index 1d9fb5fd..6223e281 100644 --- a/crates/pet/src/main.rs +++ b/crates/pet/src/main.rs @@ -37,6 +37,7 @@ enum Commands { cache_directory: Option, /// Display verbose output (defaults to warnings). + /// Note: Has no effect when --json is used, as logging is disabled to avoid polluting JSON output. #[arg(short, long)] verbose: bool,