Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
84 changes: 84 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,90 @@ 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
```

- **Find all environments and output as JSON**:
```bash
pet find --json
```

- **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
Expand Down
70 changes: 70 additions & 0 deletions crates/pet-reporter/src/json.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// 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<EnvManager>,
pub environments: Vec<PythonEnvironment>,
}

/// Reporter that collects environments and managers for JSON output
pub struct JsonReporter {
managers: Arc<Mutex<Vec<EnvManager>>>,
environments: Arc<Mutex<Vec<PythonEnvironment>>>,
}

impl Default for JsonReporter {
fn default() -> Self {
Self::new()
}
}

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()
}
1 change: 1 addition & 0 deletions crates/pet-reporter/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,6 @@
pub mod cache;
pub mod collect;
pub mod environment;
pub mod json;
pub mod jsonrpc;
pub mod stdio;
162 changes: 93 additions & 69 deletions crates/pet/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,18 @@ pub struct FindOptions {
pub workspace_only: bool,
pub cache_directory: Option<PathBuf>,
pub kind: Option<PythonEnvironmentKind>,
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 {
Expand Down Expand Up @@ -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();

Expand Down Expand Up @@ -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::<BTreeMap<String, u16>>()
{
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::<BTreeMap<String, u16>>()
{
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::<BTreeMap<String, u16>>()
{
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::<BTreeMap<String, u16>>()
{
println!("{k:<20} : {v:?}");
}
println!()
}
println!()
}
}
}
Expand Down
Loading
Loading