From 763050e64446b044b1cf9b1ab9927aed0d01fc9a Mon Sep 17 00:00:00 2001 From: Karthik Nadig Date: Wed, 2 Oct 2024 10:30:48 -0700 Subject: [PATCH 01/11] Always build on main update (#160) --- azure-pipelines/pre-release.yml | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/azure-pipelines/pre-release.yml b/azure-pipelines/pre-release.yml index c62c5aee..125423ff 100644 --- a/azure-pipelines/pre-release.yml +++ b/azure-pipelines/pre-release.yml @@ -1,15 +1,10 @@ # Run on a schedule -trigger: none +trigger: + branches: + include: + - main pr: none -schedules: - - cron: "0 10 * * 1-5" # 10AM UTC (2AM PDT) MON-FRI (VS Code Pre-release builds at 9PM PDT) - displayName: Nightly Pre-Release Schedule - always: false # only run if there are source code changes - branches: - include: - - main - resources: repositories: - repository: templates From 451025f199c056f27d6b3551e9ce61612a1f4967 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Fri, 4 Oct 2024 01:45:47 +1000 Subject: [PATCH 02/11] Expand the environments directories (#162) Fixes #161 --- crates/pet/src/find.rs | 17 ++++++++++++++++- crates/pet/tests/ci_homebrew_container.rs | 5 ++++- 2 files changed, 20 insertions(+), 2 deletions(-) diff --git a/crates/pet/src/find.rs b/crates/pet/src/find.rs index 3b835281..89105235 100644 --- a/crates/pet/src/find.rs +++ b/crates/pet/src/find.rs @@ -141,13 +141,28 @@ pub fn find_and_report_envs( s.spawn(|| { let start = std::time::Instant::now(); if search_global { + let mut possible_environments = vec![]; + + // These are directories that contain environments, hence enumerate these directories. + for directory in environment_directories { + if let Ok(reader) = fs::read_dir(directory) { + possible_environments.append( + &mut reader + .filter_map(Result::ok) + .filter(|d| d.file_type().is_ok_and(|f| f.is_dir())) + .map(|p| p.path()) + .collect(), + ); + } + } + let search_paths: Vec = [ list_global_virtual_envs_paths( environment.get_env_var("WORKON_HOME".into()), environment.get_env_var("XDG_DATA_HOME".into()), environment.get_user_home(), ), - environment_directories, + possible_environments, ] .concat(); let global_env_search_paths: Vec = diff --git a/crates/pet/tests/ci_homebrew_container.rs b/crates/pet/tests/ci_homebrew_container.rs index 16d454d9..0275df39 100644 --- a/crates/pet/tests/ci_homebrew_container.rs +++ b/crates/pet/tests/ci_homebrew_container.rs @@ -50,7 +50,7 @@ fn verify_python_in_homebrew_contaner() { let python3_12 = PythonEnvironment { kind: Some(PythonEnvironmentKind::Homebrew), executable: Some(PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3")), - version: Some("3.12.6".to_string()), // This can change on CI, so we don't check it + version: Some("3.12.7".to_string()), // This can change on CI, so we don't check it symlinks: Some(vec![ PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3"), PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3.12"), @@ -103,6 +103,9 @@ fn verify_python_in_homebrew_contaner() { .filter(|p| { !p.to_string_lossy() .contains(&env.version.clone().unwrap_or_default()) + && !p + .to_string_lossy() + .contains(&python_env.version.clone().unwrap_or_default()) }) .collect::>(); assert_eq!( From 1b1e118ba16bbbb25e6e5d52e5b3b2175bb94eaa Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 7 Oct 2024 03:28:02 +1100 Subject: [PATCH 03/11] Conda envs in envs dir cannot be conda install dir (#164) Fixes https://github.com/microsoft/vscode-python/issues/24247 --- crates/pet-conda/src/utils.rs | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/crates/pet-conda/src/utils.rs b/crates/pet-conda/src/utils.rs index b6fcdc94..2cc7276e 100644 --- a/crates/pet-conda/src/utils.rs +++ b/crates/pet-conda/src/utils.rs @@ -5,8 +5,29 @@ use std::path::{Path, PathBuf}; /// conda-meta must exist as this contains a mandatory `history` file. pub fn is_conda_install(path: &Path) -> bool { - (path.join("condabin").exists() || path.join("envs").exists()) + if (path.join("condabin").exists() || path.join("envs").exists()) && path.join("conda-meta").exists() + { + // For https://github.com/microsoft/vscode-python/issues/24247 + // Possible the env has a condabin or envs folder but its not the install directory. + // & in fact its just a regular conda env. + // Easy way is to check if the grand parent folder is a conda install directory. + if let Some(parent) = path.parent() { + if let Some(parent) = parent.parent() { + // If the grand parent is a conda install directory, + // then this is definitely not a conda install dir. + if (parent.join("condabin").exists() || parent.join("envs").exists()) + && parent.join("conda-meta").exists() + { + return false; + } + } + } + + return true; + } + + false } /// conda-meta must exist as this contains a mandatory `history` file. From ed4edd6a507dce6f5cf725538d86fdb96d3a6e16 Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 21 Oct 2024 13:35:58 +1100 Subject: [PATCH 04/11] Enable triggering PR workflow and fix tests (#168) --- .github/workflows/pr-check.yml | 9 +++--- crates/pet-homebrew/src/environments.rs | 2 +- crates/pet-homebrew/src/lib.rs | 6 ++-- crates/pet-homebrew/src/sym_links.rs | 6 ++-- crates/pet/tests/ci_homebrew_container.rs | 39 ++++++++++++++++++----- 5 files changed, 43 insertions(+), 19 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index 1420a20c..cb327c34 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -13,6 +13,7 @@ on: - release* - release/* - release-* + workflow_dispatch: jobs: tests: @@ -133,7 +134,7 @@ jobs: if: startsWith( matrix.os, 'ubuntu') || startsWith( matrix.os, 'macos') run: | pyenv install --list - pyenv install 3.12.6 3.8.19 + pyenv install 3.13.0 3.12.7 3.8.20 shell: bash # pyenv-win install list has not updated for a while @@ -179,7 +180,7 @@ jobs: - name: Find Environments if: matrix.run_cli == 'yes' - run: cargo run --release --target ${{ matrix.target }} + run: cargo run --release --target ${{ matrix.target }} -- find -v -l shell: bash - name: Run Tests @@ -351,7 +352,7 @@ jobs: shell: bash - name: Find Environments - run: cargo run --release --target ${{ matrix.target }} + run: cargo run --release --target ${{ matrix.target }} -- find -v -l shell: bash - name: Run Tests @@ -409,7 +410,7 @@ jobs: shell: bash - name: Find Environments - run: cargo run --release --target ${{ matrix.target }} + run: cargo run --release --target ${{ matrix.target }} -- find -v -l shell: bash - name: Run Tests diff --git a/crates/pet-homebrew/src/environments.rs b/crates/pet-homebrew/src/environments.rs index 905b35a9..4aa82ae0 100644 --- a/crates/pet-homebrew/src/environments.rs +++ b/crates/pet-homebrew/src/environments.rs @@ -132,7 +132,7 @@ fn get_prefix(_resolved_file: &Path) -> Option { // let captures = reg_ex.captures(resolved_file)?; // let version = captures.get(1).map(|m| m.as_str()).unwrap_or_default(); // let full_version = captures.get(2).map(|m| m.as_str()).unwrap_or_default(); - // // SysPrefix- /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8 + // // SysPrefix- /usr/local/Cellar/python@3.8/3.8.20/Frameworks/Python.framework/Versions/3.8 // let sys_prefix = PathBuf::from(format!( // "/usr/local/Cellar/python@{}/{}/Frameworks/Python.framework/Versions/{}", // version, full_version, version diff --git a/crates/pet-homebrew/src/lib.rs b/crates/pet-homebrew/src/lib.rs index 4f5be90c..575f32b2 100644 --- a/crates/pet-homebrew/src/lib.rs +++ b/crates/pet-homebrew/src/lib.rs @@ -112,9 +112,9 @@ fn from(env: &PythonEnv) -> Option { } else if resolved_file.starts_with("/usr/local/Cellar") { // Symlink - /usr/local/bin/python3.8 // Symlink - /usr/local/opt/python@3.8/bin/python3.8 - // Symlink - /usr/local/Cellar/python@3.8/3.8.19/bin/python3.8 - // Real exe - /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8/bin/python3.8 - // SysPrefix- /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8 + // Symlink - /usr/local/Cellar/python@3.8/3.8.20/bin/python3.8 + // Real exe - /usr/local/Cellar/python@3.8/3.8.20/Frameworks/Python.framework/Versions/3.8/bin/python3.8 + // SysPrefix- /usr/local/Cellar/python@3.8/3.8.20/Frameworks/Python.framework/Versions/3.8 get_python_info( &PathBuf::from("/usr/local/bin").join(exe_file_name), &resolved_file, diff --git a/crates/pet-homebrew/src/sym_links.rs b/crates/pet-homebrew/src/sym_links.rs index 3e717b53..9bea4adb 100644 --- a/crates/pet-homebrew/src/sym_links.rs +++ b/crates/pet-homebrew/src/sym_links.rs @@ -129,13 +129,13 @@ pub fn get_known_symlinks_impl( None => vec![], } } else if symlink_resolved_python_exe.starts_with("/usr/local/Cellar") { - // Real exe - /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8/bin/python3.8 + // Real exe - /usr/local/Cellar/python@3.8/3.8.20/Frameworks/Python.framework/Versions/3.8/bin/python3.8 // Known symlinks include // /usr/local/bin/python3.8 // /usr/local/opt/python@3.8/bin/python3.8 - // /usr/local/Cellar/python@3.8/3.8.19/bin/python3.8 - // /usr/local/Cellar/python@3.8/3.8.19/Frameworks/Python.framework/Versions/3.8/bin/python3.8 + // /usr/local/Cellar/python@3.8/3.8.20/bin/python3.8 + // /usr/local/Cellar/python@3.8/3.8.20/Frameworks/Python.framework/Versions/3.8/bin/python3.8 match PYTHON_VERSION.captures(symlink_resolved_python_exe.to_str().unwrap_or_default()) { Some(captures) => match captures.get(1) { Some(version) => { diff --git a/crates/pet/tests/ci_homebrew_container.rs b/crates/pet/tests/ci_homebrew_container.rs index 0275df39..1e16e3ec 100644 --- a/crates/pet/tests/ci_homebrew_container.rs +++ b/crates/pet/tests/ci_homebrew_container.rs @@ -47,21 +47,41 @@ fn verify_python_in_homebrew_contaner() { let environments = reporter.environments.lock().unwrap().clone(); + // let python3_12 = PythonEnvironment { + // kind: Some(PythonEnvironmentKind::Homebrew), + // executable: Some(PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3")), + // version: Some("3.13.0".to_string()), // This can change on CI, so we don't check it + // symlinks: Some(vec![ + // // For older versions of Python, we do not have a tonne of symlinks, + // // E.g. for 3.12.7 (which was the latest at some point, at a lot of symlinks) + // // As soon as 3.13 was shipped, the number of symlinks in 3.12.7 was the same as 3.11 (very few) + // // I.e. only the latest versions of python have a lot of symlinks, debt to take these into account and simplify the tests + // PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3"), + // PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3.13"), + // PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python3/bin/python3"), + // PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python3/bin/python3.13"), + // PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python@3/bin/python3"), + // PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python@3/bin/python3.13"), + // PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python@3.13/bin/python3"), + // PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python@3.13/bin/python3.13"), + // // On CI the Python version can change with minor updates, so we don't check the full version. + // // PathBuf::from("/home/linuxbrew/.linuxbrew/Cellar/python@3.13/3.13.0/bin/python3.13"), + // ]), + // ..Default::default() + // }; let python3_12 = PythonEnvironment { kind: Some(PythonEnvironmentKind::Homebrew), - executable: Some(PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3")), + executable: Some(PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3.12")), version: Some("3.12.7".to_string()), // This can change on CI, so we don't check it symlinks: Some(vec![ - PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3"), + // For older versions of Python, we do not have a tonne of symlinks, + // E.g. for 3.12.7 (which was the latest at some point, at a lot of symlinks) + // As soon as 3.13 was shipped, the number of symlinks in 3.12.7 was the same as 3.11 (very few) + // I.e. only the latest versions of python have a lot of symlinks, debt to take these into account and simplify the tests PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3.12"), - PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python3/bin/python3"), - PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python3/bin/python3.12"), - PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python@3/bin/python3"), - PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python@3/bin/python3.12"), - PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python@3.12/bin/python3"), PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python@3.12/bin/python3.12"), // On CI the Python version can change with minor updates, so we don't check the full version. - // PathBuf::from("/home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.6/bin/python3.12"), + // PathBuf::from("/home/linuxbrew/.linuxbrew/Cellar/python@3.12/3.12.7_1/bin/python3.12"), ]), ..Default::default() }; @@ -70,6 +90,9 @@ fn verify_python_in_homebrew_contaner() { executable: Some(PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3.11")), version: Some("3.11.10".to_string()), // This can change on CI, so we don't check it symlinks: Some(vec![ + // For older versions of Python, we do not have a tonne of symlinks, + // E.g. for 3.12.7 (which was the latest at some point, at a lot of symlinks) + // As soon as 3.13 was shipped, the number of symlinks in 3.12.7 was the same as 3.11 (very few) PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3.11"), PathBuf::from("/home/linuxbrew/.linuxbrew/opt/python@3.11/bin/python3.11"), // On CI the Python version can change with minor updates, so we don't check the full version. From ffcbf3f28c46633abd5448a52b1f396c322e0d6c Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Tue, 22 Oct 2024 09:04:08 +1100 Subject: [PATCH 05/11] Ensure venv directories are never ignored (#167) Fixes #166 --- crates/pet/src/find.rs | 17 +++++++++++++++-- crates/pet/src/jsonrpc.rs | 8 ++++++++ 2 files changed, 23 insertions(+), 2 deletions(-) diff --git a/crates/pet/src/find.rs b/crates/pet/src/find.rs index 89105235..a4bc9e3e 100644 --- a/crates/pet/src/find.rs +++ b/crates/pet/src/find.rs @@ -138,13 +138,14 @@ pub fn find_and_report_envs( .insert("Path", start.elapsed()); }); // Step 3: Search in some global locations for virtual envs. + let environment_directories_search = environment_directories.clone(); s.spawn(|| { let start = std::time::Instant::now(); if search_global { let mut possible_environments = vec![]; // These are directories that contain environments, hence enumerate these directories. - for directory in environment_directories { + for directory in environment_directories_search { if let Ok(reader) = fs::read_dir(directory) { possible_environments.append( &mut reader @@ -207,12 +208,14 @@ pub fn find_and_report_envs( get_search_paths_from_env_variables(environment); for workspace_folder in workspace_directories { let global_env_search_paths = global_env_search_paths.clone(); + let environment_directories = environment_directories.clone(); s.spawn(move || { find_python_environments_in_workspace_folder_recursive( &workspace_folder, reporter, locators, &global_env_search_paths, + &environment_directories, ); }); } @@ -248,6 +251,7 @@ pub fn find_python_environments_in_workspace_folder_recursive( reporter: &dyn Reporter, locators: &Arc>>, global_env_search_paths: &[PathBuf], + environment_directories: &[PathBuf], ) { // When searching in a directory, give preference to some paths. let paths_to_search_first = vec![ @@ -278,7 +282,16 @@ pub fn find_python_environments_in_workspace_folder_recursive( .filter_map(Result::ok) .filter(|d| d.file_type().is_ok_and(|f| f.is_dir())) .map(|p| p.path()) - .filter(should_search_for_environments_in_path) + .filter(|p| { + // If this directory is a sub directory or is in the environment_directories, then do not search in this directory. + if environment_directories.contains(p) { + return true; + } + if environment_directories.iter().any(|d| p.starts_with(d)) { + return true; + } + should_search_for_environments_in_path(p) + }) .filter(|p| !paths_to_search_first.contains(p)) { find_python_environments(vec![folder], reporter, locators, true, &[]); diff --git a/crates/pet/src/jsonrpc.rs b/crates/pet/src/jsonrpc.rs index 6f7636d7..8a805ace 100644 --- a/crates/pet/src/jsonrpc.rs +++ b/crates/pet/src/jsonrpc.rs @@ -402,6 +402,14 @@ pub fn handle_find(context: Arc, id: u32, params: Value) { &reporter, &context.locators, &global_env_search_paths, + context + .configuration + .read() + .unwrap() + .clone() + .environment_directories + .as_deref() + .unwrap_or(&[]), ); } From 6a2879ea80ea851c0783da8a42ef7766844871f3 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 14 Nov 2024 09:26:20 -0800 Subject: [PATCH 06/11] chore: enable TSA (#173) This PR enables TSA for this repository to allow maintainers to see this repository's compliance scan results more easily. --- azure-pipelines/pre-release.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/azure-pipelines/pre-release.yml b/azure-pipelines/pre-release.yml index 125423ff..ce7d0200 100644 --- a/azure-pipelines/pre-release.yml +++ b/azure-pipelines/pre-release.yml @@ -21,6 +21,10 @@ extends: signing: true buildWasm: false apiScanSoftwareVersion: 2024 # major version of `pet` for internal reporting + tsa: + enabled: true + options: + serviceTreeID: 6e6194bc-7baa-4486-86d0-9f5419626d46 buildPlatforms: - name: Linux From 76a821031a034310d233dca9d65147376934ceb8 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Thu, 14 Nov 2024 10:19:58 -0800 Subject: [PATCH 07/11] chore: fix TSA metadata (#174) --- azure-pipelines/pre-release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/azure-pipelines/pre-release.yml b/azure-pipelines/pre-release.yml index ce7d0200..904ca5ef 100644 --- a/azure-pipelines/pre-release.yml +++ b/azure-pipelines/pre-release.yml @@ -23,7 +23,8 @@ extends: apiScanSoftwareVersion: 2024 # major version of `pet` for internal reporting tsa: enabled: true - options: + config: + areaPath: "Visual Studio Code Python Extensions" serviceTreeID: 6e6194bc-7baa-4486-86d0-9f5419626d46 buildPlatforms: From 370854423d206cd2e5bac8b2c1857ac586497e86 Mon Sep 17 00:00:00 2001 From: Raymond Zhao <7199958+rzhao271@users.noreply.github.com> Date: Tue, 26 Nov 2024 13:00:34 -0800 Subject: [PATCH 08/11] chore: add symbol publishing and tsa (#176) Verification build: https://dev.azure.com/monacotools/Monaco/_build/results?buildId=307217&view=results --- azure-pipelines/stable.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/azure-pipelines/stable.yml b/azure-pipelines/stable.yml index 64058ba1..4b8ab328 100644 --- a/azure-pipelines/stable.yml +++ b/azure-pipelines/stable.yml @@ -17,7 +17,13 @@ extends: binaryName: "pet" signing: true buildWasm: false + apiScanPublishSymbols: true apiScanSoftwareVersion: 2024 # major version of `pet` for internal reporting + tsa: + enabled: true + config: + areaPath: "Visual Studio Code Python Extensions" + serviceTreeID: 6e6194bc-7baa-4486-86d0-9f5419626d46 buildPlatforms: - name: Linux From 15124b5b945c533572db76cc2bf31e19d6f76747 Mon Sep 17 00:00:00 2001 From: Renan Santos Date: Mon, 9 Dec 2024 14:15:13 -0300 Subject: [PATCH 09/11] Add Pixi locator (#172) Fixes https://github.com/microsoft/python-environment-tools/issues/46 I'd like to reopen the discussion about adding support for Pixi given the community interest after the above issue was closed and to bring feature parity with the `js` locator. Because Pixi environments have a unique directory layout, the proposed solution is simple enough that it doesn't need any process spawning. Sister PR: - https://github.com/microsoft/vscode-python/pull/24442 --- .devcontainer/linux-homebrew/Dockerfile | 5 +- .github/workflows/pr-check.yml | 23 +++++- Cargo.lock | 12 ++++ crates/pet-conda/README.md | 5 -- crates/pet-core/src/lib.rs | 1 + crates/pet-core/src/python_environment.rs | 1 + crates/pet-pixi/Cargo.toml | 13 ++++ crates/pet-pixi/README.md | 11 +++ crates/pet-pixi/src/lib.rs | 87 +++++++++++++++++++++++ crates/pet/Cargo.toml | 1 + crates/pet/README.md | 26 +++---- crates/pet/src/find.rs | 18 ++++- crates/pet/src/locators.rs | 16 +++-- docs/JSONRPC.md | 1 + 14 files changed, 193 insertions(+), 27 deletions(-) create mode 100644 crates/pet-pixi/Cargo.toml create mode 100644 crates/pet-pixi/README.md create mode 100644 crates/pet-pixi/src/lib.rs diff --git a/.devcontainer/linux-homebrew/Dockerfile b/.devcontainer/linux-homebrew/Dockerfile index f79c8ddb..5b801b7b 100644 --- a/.devcontainer/linux-homebrew/Dockerfile +++ b/.devcontainer/linux-homebrew/Dockerfile @@ -10,7 +10,10 @@ RUN curl https://raw.githubusercontent.com/DonJayamanne/vscode-jupyter/container RUN echo "# To customize prompt, run `p10k configure` or edit ~/.p10k.zsh." >> ~/.zshrc RUN echo "[[ ! -f ~/.p10k.zsh ]] || source ~/.p10k.zsh" >> ~/.zshrc -# Install Pythone +# Install Python +# homebrew/brew:4.4.6 broke running `brew install` as root. +# As a workaround, running `brew update` and ignoring errors coming from it fixes `brew install`. +RUN brew update || true RUN brew install python@3.12 python@3.11 # Install Rust diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index cb327c34..a133f1b2 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -167,6 +167,23 @@ jobs: # endregion venv + # region Pixi + - name: Install Pixi + uses: prefix-dev/setup-pixi@v0.8.1 + with: + run-install: false + + - name: Create Pixi environments + run: | + pixi init + pixi add python + pixi add --feature dev python + pixi project environment add --feature dev dev + pixi install --environment dev + shell: bash + + # endregion Pixi + # Rust - name: Rust Tool Chain setup uses: dtolnay/rust-toolchain@stable @@ -395,7 +412,11 @@ jobs: # Homebrew - name: Homebrew Python if: startsWith( matrix.image, 'homebrew') - run: brew install python@3.12 python@3.11 + run: | + # homebrew/brew:4.4.6 broke running `brew install` as root. + # As a workaround, running `brew update` and ignoring errors coming from it fixes `brew install`. + brew update || true + brew install python@3.12 python@3.11 shell: bash # Rust diff --git a/Cargo.lock b/Cargo.lock index b1917e23..b8d454ab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -354,6 +354,7 @@ dependencies = [ "pet-mac-python-org", "pet-mac-xcode", "pet-pipenv", + "pet-pixi", "pet-poetry", "pet-pyenv", "pet-python-utils", @@ -525,6 +526,17 @@ dependencies = [ "pet-virtualenv", ] +[[package]] +name = "pet-pixi" +version = "0.1.0" +dependencies = [ + "log", + "msvc_spectre_libs", + "pet-conda", + "pet-core", + "pet-python-utils", +] + [[package]] name = "pet-poetry" version = "0.1.0" diff --git a/crates/pet-conda/README.md b/crates/pet-conda/README.md index cbe9a2f6..429ef7ac 100644 --- a/crates/pet-conda/README.md +++ b/crates/pet-conda/README.md @@ -36,11 +36,6 @@ - Thus using the `history` file we can find the conda installation folder. This is useful in cases where conda environments are created using `-p` option. -## Known issues - -- Note: pixi seems to use conda envs internall, hence its possible to falsely identify a pixi env as a conda env. -- However pixi is not supported by this tool, hence thats not a concern. - ## Miscellanous - What if conda is installed in some custom locations that we have no idea about? diff --git a/crates/pet-core/src/lib.rs b/crates/pet-core/src/lib.rs index 033ec44b..8d123469 100644 --- a/crates/pet-core/src/lib.rs +++ b/crates/pet-core/src/lib.rs @@ -47,6 +47,7 @@ pub enum LocatorKind { MacPythonOrg, MacXCode, PipEnv, + Pixi, Poetry, PyEnv, Venv, diff --git a/crates/pet-core/src/python_environment.rs b/crates/pet-core/src/python_environment.rs index b967e129..9531a70a 100644 --- a/crates/pet-core/src/python_environment.rs +++ b/crates/pet-core/src/python_environment.rs @@ -12,6 +12,7 @@ use crate::{arch::Architecture, manager::EnvManager}; #[derive(Parser, ValueEnum, Serialize, Deserialize, Clone, Copy, PartialEq, Eq, Debug, Hash)] pub enum PythonEnvironmentKind { Conda, + Pixi, Homebrew, Pyenv, GlobalPaths, // Python found in global locations like PATH, /usr/bin etc. diff --git a/crates/pet-pixi/Cargo.toml b/crates/pet-pixi/Cargo.toml new file mode 100644 index 00000000..cdeb5095 --- /dev/null +++ b/crates/pet-pixi/Cargo.toml @@ -0,0 +1,13 @@ +[package] +name = "pet-pixi" +version = "0.1.0" +edition = "2021" + +[target.'cfg(target_os = "windows")'.dependencies] +msvc_spectre_libs = { version = "0.1.1", features = ["error"] } + +[dependencies] +pet-conda = { path = "../pet-conda" } +pet-core = { path = "../pet-core" } +pet-python-utils = { path = "../pet-python-utils" } +log = "0.4.21" diff --git a/crates/pet-pixi/README.md b/crates/pet-pixi/README.md new file mode 100644 index 00000000..f177e167 --- /dev/null +++ b/crates/pet-pixi/README.md @@ -0,0 +1,11 @@ +# Pixi + +## Notes + +- Pixi environments are detected by: + - Searching for Python interpreters in `.pixi/envs` subdirectories within workspace folders + - Checking for a `conda-meta/pixi` file in potential Pixi environment directories (`.pixi/envs/{env_name}`) + - Determining the version of the Python interpreter from the `conda-meta/python-{version}.json` file + +This process ensures fast detection without spawning processes. +Note that the Pixi locator should run before Conda since Conda could incorrectly identify Pixi environments as Conda environments. diff --git a/crates/pet-pixi/src/lib.rs b/crates/pet-pixi/src/lib.rs new file mode 100644 index 00000000..b7adfde9 --- /dev/null +++ b/crates/pet-pixi/src/lib.rs @@ -0,0 +1,87 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +use std::path::{Path, PathBuf}; + +use pet_conda::package::{CondaPackageInfo, Package}; +use pet_core::{ + env::PythonEnv, + python_environment::{PythonEnvironment, PythonEnvironmentBuilder, PythonEnvironmentKind}, + reporter::Reporter, + Locator, LocatorKind, +}; +use pet_python_utils::executable::find_executables; + +pub fn is_pixi_env(path: &Path) -> bool { + path.join("conda-meta").join("pixi").is_file() +} + +fn get_pixi_prefix(env: &PythonEnv) -> Option { + env.prefix.clone().or_else(|| { + env.executable.parent().and_then(|parent_dir| { + if is_pixi_env(parent_dir) { + Some(parent_dir.to_path_buf()) + } else if parent_dir.ends_with("bin") || parent_dir.ends_with("Scripts") { + parent_dir + .parent() + .filter(|parent| is_pixi_env(parent)) + .map(|parent| parent.to_path_buf()) + } else { + None + } + }) + }) +} + +pub struct Pixi {} + +impl Pixi { + pub fn new() -> Pixi { + Pixi {} + } +} +impl Default for Pixi { + fn default() -> Self { + Self::new() + } +} + +impl Locator for Pixi { + fn get_kind(&self) -> LocatorKind { + LocatorKind::Pixi + } + fn supported_categories(&self) -> Vec { + vec![PythonEnvironmentKind::Pixi] + } + + fn try_from(&self, env: &PythonEnv) -> Option { + get_pixi_prefix(env).and_then(|prefix| { + if !is_pixi_env(&prefix) { + return None; + } + + let name = prefix + .file_name() + .and_then(|name| name.to_str()) + .unwrap_or_default() + .to_string(); + + let symlinks = find_executables(&prefix); + + let version = CondaPackageInfo::from(&prefix, &Package::Python) + .map(|package_info| package_info.version); + + Some( + PythonEnvironmentBuilder::new(Some(PythonEnvironmentKind::Pixi)) + .executable(Some(env.executable.clone())) + .name(Some(name)) + .prefix(Some(prefix)) + .symlinks(Some(symlinks)) + .version(version) + .build(), + ) + }) + } + + fn find(&self, _reporter: &dyn Reporter) {} +} diff --git a/crates/pet/Cargo.toml b/crates/pet/Cargo.toml index b8e8bfa1..11e045a7 100644 --- a/crates/pet/Cargo.toml +++ b/crates/pet/Cargo.toml @@ -16,6 +16,7 @@ pet-homebrew = { path = "../pet-homebrew" } [dependencies] pet-core = { path = "../pet-core" } pet-conda = { path = "../pet-conda" } +pet-pixi = { path = "../pet-pixi" } pet-jsonrpc = { path = "../pet-jsonrpc" } pet-fs = { path = "../pet-fs" } pet-pyenv = { path = "../pet-pyenv" } diff --git a/crates/pet/README.md b/crates/pet/README.md index 4ddd9cdd..ef275fbc 100644 --- a/crates/pet/README.md +++ b/crates/pet/README.md @@ -66,53 +66,55 @@ It could have been created by other tools like `poetry`, `pipenv`, etc. Hence we 4. Homebrew These are always stored in a custom (global) lcoation. -5. Conda + +5. Pixi + Pixi should run before Conda as its environments could be falsely identified as Conda environments. + +6. Conda - These are always stored in a custom (global) location. - At this stage Conda envs cannot be treated as anything else -- Note: pixi seems to use conda envs internall, hence its possible to falsely identify a pixi env as a conda env. -- However pixi is not supported by this tool, hence thats not a concern. -6. Poetry +7. Poetry - These are always stored in a custom (global) location. - Environments are created with a special name based on the hash of the project folder. - This needs to happen before others as these environments are general virtual environments. -7. Pipenv +8. Pipenv - These are always stored in a custom (global) location. - This needs to happen before others as these environments are general virtual environments. -8. Virtualenvwrapper +9. Virtualenvwrapper - These are always stored in a custom (global) location. - This needs to happen before others as these environments are general virtual environments. -9. Vevn +10. Vevn - A virtual environment that has a `pyvenv.cfg` file. - Note, this is a fallback for all other virtual environments. -10. Virtualenv +11. Virtualenv - A virtual environment that does not have a `pyvenv.cfg` file but has activation scripts such as `/bin/activate` or `Scripts/activate.bat` or the like. - Note, this is a fallback for all other environments. - This must happen after conda env discovery, as conda envs have activation scripts as well. -11. Mac XCode +12. Mac XCode - These are always stored in a custom (global) location. -12. Mac Command Line Tools +13. Mac Command Line Tools - These are always stored in a custom (global) location. -13. Mac Python Org +14. Mac Python Org - These are always stored in a custom (global) location. -14. Linux Python +15. Linux Python - These are always stored in a custom (global) location. diff --git a/crates/pet/src/find.rs b/crates/pet/src/find.rs index a4bc9e3e..63aefd63 100644 --- a/crates/pet/src/find.rs +++ b/crates/pet/src/find.rs @@ -10,6 +10,7 @@ use pet_core::reporter::Reporter; use pet_core::{Configuration, Locator, LocatorKind}; use pet_env_var_path::get_search_paths_from_env_variables; use pet_global_virtualenvs::list_global_virtual_envs_paths; +use pet_pixi::is_pixi_env; use pet_python_utils::executable::{ find_executable, find_executables, should_search_for_environments_in_path, }; @@ -254,7 +255,7 @@ pub fn find_python_environments_in_workspace_folder_recursive( environment_directories: &[PathBuf], ) { // When searching in a directory, give preference to some paths. - let paths_to_search_first = vec![ + let mut paths_to_search_first = vec![ // Possible this is a virtual env workspace_folder.to_path_buf(), // Optimize for finding these first. @@ -264,6 +265,15 @@ pub fn find_python_environments_in_workspace_folder_recursive( workspace_folder.join("venv"), ]; + // Add all subdirectories of .pixi/envs/** + if let Ok(reader) = fs::read_dir(workspace_folder.join(".pixi").join("envs")) { + reader + .filter_map(Result::ok) + .filter(|d| d.file_type().is_ok_and(|f| f.is_dir())) + .map(|p| p.path()) + .for_each(|p| paths_to_search_first.push(p)); + } + // Possible this is an environment. find_python_environments_in_paths_with_locators( paths_to_search_first.clone(), @@ -274,7 +284,11 @@ pub fn find_python_environments_in_workspace_folder_recursive( ); // If this is a virtual env folder, no need to scan this. - if is_virtualenv_dir(workspace_folder) || is_conda_env(workspace_folder) { + // Note: calling is_pixi_env after is_conda_env is redundant but kept for consistency. + if is_virtualenv_dir(workspace_folder) + || is_conda_env(workspace_folder) + || is_pixi_env(workspace_folder) + { return; } if let Ok(reader) = fs::read_dir(workspace_folder) { diff --git a/crates/pet/src/locators.rs b/crates/pet/src/locators.rs index 3047217d..fa5b0cc7 100644 --- a/crates/pet/src/locators.rs +++ b/crates/pet/src/locators.rs @@ -15,6 +15,7 @@ use pet_mac_commandlinetools::MacCmdLineTools; use pet_mac_python_org::MacPythonOrg; use pet_mac_xcode::MacXCode; use pet_pipenv::PipEnv; +use pet_pixi::Pixi; use pet_poetry::Poetry; use pet_pyenv::PyEnv; use pet_python_utils::env::ResolvedPythonEnv; @@ -48,10 +49,13 @@ pub fn create_locators( // 3. Pyenv Python locators.push(Arc::new(PyEnv::from(environment, conda_locator.clone()))); - // 4. Conda Python + // 4. Pixi + locators.push(Arc::new(Pixi::new())); + + // 5. Conda Python locators.push(conda_locator); - // 5. Support for Virtual Envs + // 6. Support for Virtual Envs // The order of these matter. // Basically PipEnv is a superset of VirtualEnvWrapper, which is a superset of Venv, which is a superset of VirtualEnv. locators.push(poetry_locator); @@ -61,7 +65,7 @@ pub fn create_locators( // VirtualEnv is the most generic, hence should be the last. locators.push(Arc::new(VirtualEnv::new())); - // 6. Homebrew Python + // 7. Homebrew Python if cfg!(unix) { #[cfg(unix)] use pet_homebrew::Homebrew; @@ -71,14 +75,14 @@ pub fn create_locators( locators.push(Arc::new(homebrew_locator)); } - // 7. Global Mac Python - // 8. CommandLineTools Python & xcode + // 8. Global Mac Python + // 9. CommandLineTools Python & xcode if std::env::consts::OS == "macos" { locators.push(Arc::new(MacXCode::new())); locators.push(Arc::new(MacCmdLineTools::new())); locators.push(Arc::new(MacPythonOrg::new())); } - // 9. Global Linux Python + // 10. Global Linux Python // All other Linux (not mac, & not windows) // THIS MUST BE LAST if std::env::consts::OS != "macos" && std::env::consts::OS != "windows" { diff --git a/docs/JSONRPC.md b/docs/JSONRPC.md index 31006e48..e18daef1 100644 --- a/docs/JSONRPC.md +++ b/docs/JSONRPC.md @@ -143,6 +143,7 @@ interface ResolveParams { enum PythonEnvironmentKind { Conda, + Pixi, Homebrew, Pyenv, GlobalPaths, // Python found in global locations like PATH, /usr/bin etc. From 1abe5cec5ebfbe97ca71746a4cfc7fe89bddf8e0 Mon Sep 17 00:00:00 2001 From: Courtney Webster <60238438+cwebster-99@users.noreply.github.com> Date: Wed, 11 Dec 2024 10:38:40 -0600 Subject: [PATCH 10/11] Update README.md (#179) Adding more details based on discussion in https://github.com/microsoft/python-environment-tools/issues/98 --------- Co-authored-by: Karthik Nadig --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index b483921b..64ca2e3e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,8 @@ # Python environment tools for Visual Studio Code -Performant Python environment tooling and support, such as locating all global Python installs and virtual environments. +Performant Python environment tooling and support, such as locating all global Python installs and virtual environments. + +This project will be consumed by the [Python extension](https://marketplace.visualstudio.com/items?itemName=ms-python.python) directly. You can find the code to consume `pet` in the Python extension [source code](https://github.com/microsoft/vscode-python/blob/main/src/client/pythonEnvironments/base/locators/common/nativePythonFinder.ts). For more information on JSNORPC requests/notifications for this tool, please reference [/docs/JSONRPC.md](https://github.com/microsoft/python-environment-tools/blob/main/docs/JSONRPC.md). ## Environment Types Supported From ebeb53f7653a7c3c4d7ab2086eb241b6aafaa56b Mon Sep 17 00:00:00 2001 From: Don Jayamanne Date: Mon, 10 Feb 2025 20:35:14 +1100 Subject: [PATCH 11/11] Prefer windows vars on windows OS and more logging (#188) For #185 --- .github/workflows/pr-check.yml | 26 +++++++++---------- crates/pet-conda/src/conda_rc.rs | 1 + crates/pet-conda/src/environment_locations.rs | 3 ++- crates/pet-conda/src/environments.rs | 4 +-- crates/pet-core/src/os_environment.rs | 12 ++++++++- crates/pet-poetry/src/pyproject_toml.rs | 21 ++++++++++++++- crates/pet/tests/ci_homebrew_container.rs | 4 +-- 7 files changed, 51 insertions(+), 20 deletions(-) diff --git a/.github/workflows/pr-check.yml b/.github/workflows/pr-check.yml index a133f1b2..ea4631e6 100644 --- a/.github/workflows/pr-check.yml +++ b/.github/workflows/pr-check.yml @@ -45,7 +45,7 @@ jobs: - name: Set Python to PATH uses: actions/setup-python@v5 with: - python-version: "3.x" + python-version: "3.12" - name: Homebrew Python if: startsWith( matrix.os, 'macos') @@ -134,7 +134,7 @@ jobs: if: startsWith( matrix.os, 'ubuntu') || startsWith( matrix.os, 'macos') run: | pyenv install --list - pyenv install 3.13.0 3.12.7 3.8.20 + pyenv install 3.13.0 3.12.8 3.8.20 shell: bash # pyenv-win install list has not updated for a while @@ -248,7 +248,7 @@ jobs: if: startsWith( matrix.feature, 'ci-poetry') uses: actions/setup-python@v5 with: - python-version: "3.x" + python-version: "3.12" - name: Set Python 3.12 to PATH if: startsWith( matrix.feature, 'ci-poetry') @@ -304,48 +304,48 @@ jobs: virtualenvs-path: ~/my-custom-path installer-parallel: true - - name: Petry exe + - name: Poetry exe if: startsWith( matrix.feature, 'ci-poetry') run: which poetry shell: bash - - name: Petry config + - name: Poetry config if: startsWith( matrix.feature, 'ci-poetry') run: poetry config --list shell: bash - - name: Petry setup + - name: Poetry setup if: startsWith( matrix.feature, 'ci-poetry') # We want to have 2 envs for this poetry project 3.12 and 3.11. run: poetry init --name=pet-test --python=^3.11 -q -n shell: bash - - name: Petry virtual env setup 3.12 + - name: Poetry virtual env setup 3.12 if: startsWith( matrix.feature, 'ci-poetry') && startsWith( matrix.os, 'ubuntu') run: poetry env use 3.12 shell: bash - - name: Petry virtual env setup 3.12 + - name: Poetry virtual env setup 3.12 if: startsWith( matrix.feature, 'ci-poetry') && startsWith( matrix.os, 'windows') run: poetry env use $PYTHON_3_12_PATH shell: bash - - name: Petry virtual env setup 3.11 + - name: Poetry virtual env setup 3.11 if: startsWith( matrix.feature, 'ci-poetry') && startsWith( matrix.os, 'ubuntu') run: poetry env use 3.11 shell: bash - - name: Petry virtual env setup 3.11 + - name: Poetry virtual env setup 3.11 if: startsWith( matrix.feature, 'ci-poetry') && startsWith( matrix.os, 'windows') run: poetry env use $PYTHON_3_11_PATH shell: bash - - name: Petry list envs + - name: Poetry list envs if: startsWith( matrix.feature, 'ci-poetry') run: poetry env list shell: bash - - name: Petry pyproject.toml + - name: Poetry pyproject.toml if: startsWith( matrix.feature, 'ci-poetry') run: cat pyproject.toml shell: bash @@ -414,7 +414,7 @@ jobs: if: startsWith( matrix.image, 'homebrew') run: | # homebrew/brew:4.4.6 broke running `brew install` as root. - # As a workaround, running `brew update` and ignoring errors coming from it fixes `brew install`. + # As a workaround, running `brew update` and ignoring errors coming from it fixes `brew install`. brew update || true brew install python@3.12 python@3.11 shell: bash diff --git a/crates/pet-conda/src/conda_rc.rs b/crates/pet-conda/src/conda_rc.rs index 98990f8c..eb8fdb03 100644 --- a/crates/pet-conda/src/conda_rc.rs +++ b/crates/pet-conda/src/conda_rc.rs @@ -216,6 +216,7 @@ fn get_conda_conda_rc_from_path(conda_rc: &PathBuf) -> Option { if env_dirs.is_empty() && files.is_empty() { None } else { + trace!("conda_rc: {:?} with env_dirs {:?}", conda_rc, env_dirs); Some(Condarc { env_dirs, files }) } } diff --git a/crates/pet-conda/src/environment_locations.rs b/crates/pet-conda/src/environment_locations.rs index 4bd65c31..744da92c 100644 --- a/crates/pet-conda/src/environment_locations.rs +++ b/crates/pet-conda/src/environment_locations.rs @@ -75,6 +75,7 @@ fn get_conda_environment_paths_from_conda_rc(env_vars: &EnvVariables) -> Vec Vec Option { let conda_exe = resolve_symlink(&conda_exe).unwrap_or(conda_exe); if let Some(cmd_line) = conda_exe.parent() { if let Some(conda_dir) = cmd_line.file_name() { - if conda_dir.to_ascii_lowercase() == "bin" - || conda_dir.to_ascii_lowercase() == "scripts" + if conda_dir.to_string_lossy().to_lowercase() == "bin" + || conda_dir.to_string_lossy().to_lowercase() == "scripts" { if let Some(conda_dir) = cmd_line.parent() { // Ensure the casing of the paths are correct. diff --git a/crates/pet-core/src/os_environment.rs b/crates/pet-core/src/os_environment.rs index f9ef61a3..65ea094d 100644 --- a/crates/pet-core/src/os_environment.rs +++ b/crates/pet-core/src/os_environment.rs @@ -133,8 +133,18 @@ impl Environment for EnvironmentApi { } } +#[cfg(windows)] +fn get_user_home() -> Option { + let home = env::var("USERPROFILE").or_else(|_| env::var("HOME")); + match home { + Ok(home) => Some(norm_case(PathBuf::from(home))), + Err(_) => None, + } +} + +#[cfg(unix)] fn get_user_home() -> Option { - let home = env::var("HOME").or_else(|_| env::var("USERPROFILE")); + let home = env::var("HOME"); match home { Ok(home) => Some(norm_case(PathBuf::from(home))), Err(_) => None, diff --git a/crates/pet-poetry/src/pyproject_toml.rs b/crates/pet-poetry/src/pyproject_toml.rs index c110f3f8..de5dc69c 100644 --- a/crates/pet-poetry/src/pyproject_toml.rs +++ b/crates/pet-poetry/src/pyproject_toml.rs @@ -8,6 +8,7 @@ use std::{ use log::{error, trace}; +#[derive(Debug)] pub struct PyProjectToml { pub name: String, } @@ -18,11 +19,13 @@ impl PyProjectToml { PyProjectToml { name } } pub fn find(path: &Path) -> Option { + trace!("Finding poetry file in {:?}", path); parse(&path.join("pyproject.toml")) } } fn parse(file: &Path) -> Option { + trace!("Parsing poetry file: {:?}", file); let contents = fs::read_to_string(file).ok()?; parse_contents(&contents, file) } @@ -38,7 +41,23 @@ fn parse_contents(contents: &str, file: &Path) -> Option { } } } - name.map(|name| PyProjectToml::new(name, file.into())) + + match name { + Some(name) => Some(PyProjectToml::new(name, file.into())), + None => { + trace!( + "Poetry project name not found in {:?}, trying the new format", + file + ); + let mut name = None; + if let Some(project) = value.get("project") { + if let Some(name_value) = project.get("name") { + name = name_value.as_str().map(|s| s.to_string()); + } + } + name.map(|name| PyProjectToml::new(name, file.into())) + } + } } Err(e) => { error!("Error parsing toml file: {:?}", e); diff --git a/crates/pet/tests/ci_homebrew_container.rs b/crates/pet/tests/ci_homebrew_container.rs index 1e16e3ec..9b0e67ea 100644 --- a/crates/pet/tests/ci_homebrew_container.rs +++ b/crates/pet/tests/ci_homebrew_container.rs @@ -72,7 +72,7 @@ fn verify_python_in_homebrew_contaner() { let python3_12 = PythonEnvironment { kind: Some(PythonEnvironmentKind::Homebrew), executable: Some(PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3.12")), - version: Some("3.12.7".to_string()), // This can change on CI, so we don't check it + version: Some("3.12.8".to_string()), // This can change on CI, so we don't check it symlinks: Some(vec![ // For older versions of Python, we do not have a tonne of symlinks, // E.g. for 3.12.7 (which was the latest at some point, at a lot of symlinks) @@ -88,7 +88,7 @@ fn verify_python_in_homebrew_contaner() { let python3_11 = PythonEnvironment { kind: Some(PythonEnvironmentKind::Homebrew), executable: Some(PathBuf::from("/home/linuxbrew/.linuxbrew/bin/python3.11")), - version: Some("3.11.10".to_string()), // This can change on CI, so we don't check it + version: Some("3.11.11".to_string()), // This can change on CI, so we don't check it symlinks: Some(vec![ // For older versions of Python, we do not have a tonne of symlinks, // E.g. for 3.12.7 (which was the latest at some point, at a lot of symlinks)