diff --git a/crates/ruff_db/src/system/path.rs b/crates/ruff_db/src/system/path.rs index 4aea0cbe8b..c20b2b19f5 100644 --- a/crates/ruff_db/src/system/path.rs +++ b/crates/ruff_db/src/system/path.rs @@ -596,6 +596,13 @@ impl AsRef for Utf8PathBuf { } } +impl AsRef for camino::Utf8Component<'_> { + #[inline] + fn as_ref(&self) -> &SystemPath { + SystemPath::new(self.as_str()) + } +} + impl AsRef for str { #[inline] fn as_ref(&self) -> &SystemPath { @@ -626,6 +633,22 @@ impl Deref for SystemPathBuf { } } +impl> FromIterator

for SystemPathBuf { + fn from_iter>(iter: I) -> Self { + let mut buf = SystemPathBuf::new(); + buf.extend(iter); + buf + } +} + +impl> Extend

for SystemPathBuf { + fn extend>(&mut self, iter: I) { + for path in iter { + self.push(path); + } + } +} + impl std::fmt::Debug for SystemPath { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { self.0.fmt(f) diff --git a/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md b/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md new file mode 100644 index 0000000000..8a60a61afb --- /dev/null +++ b/crates/ty_python_semantic/resources/mdtest/import/site_packages_discovery.md @@ -0,0 +1,59 @@ +# Tests for `site-packages` discovery + +## Ephemeral uv environments + +If you use the `--with` flag when invoking `uv run`, uv will create an "ephemeral" virtual +environment that is layered on top of the pre-existing environment. `site-packages` directories from +the pre-existing environment will be added as an import search path at runtime as well as the +`site-packages` directory from the ephemeral environment. The `VIRTUAL_ENV` environment variable +will only point to the ephemeral virtual environment, but, following uv commit +`7bba3d00d4ad1fb3daba86b98eb25d8d9e9836ae`, uv writes the `sys.prefix` path of the parent +environment to an `extends-environment` key in the ephemeral environment's `pyvenv.cfg` file. + +This test ensures that we are able to resolve imports that point to packages in either +`site-packages` directory (the one of the ephemeral environment or the one of the parent +environment) if we detect that an ephemeral uv environment has been activated. + +```toml +[environment] +python = "/.venv" +``` + +`/.venv/pyvenv.cfg`: + +```cfg +home = /doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin +implementation = CPython +uv = 0.7.6 +version_info = 3.13.2 +include-system-site-packages = false +prompt = ruff +extends-environment = /.other-environment +``` + +`/doo/doo/wop/cpython-3.13.2-macos-aarch64-none/bin/python`: + +```text +``` + +`/.venv//foo.py`: + +```py +X: int = 42 +``` + +`/.other-environment//bar.py`: + +```py +Y: "str" = "Y" +``` + +`/src/main.py`: + +```py +from foo import X +from bar import Y + +reveal_type(X) # revealed: int +reveal_type(Y) # revealed: str +``` diff --git a/crates/ty_python_semantic/src/module_resolver/resolver.rs b/crates/ty_python_semantic/src/module_resolver/resolver.rs index 49bf534e77..4013b54da1 100644 --- a/crates/ty_python_semantic/src/module_resolver/resolver.rs +++ b/crates/ty_python_semantic/src/module_resolver/resolver.rs @@ -14,7 +14,9 @@ use ruff_python_ast::PythonVersion; use crate::db::Db; use crate::module_name::ModuleName; use crate::module_resolver::typeshed::{TypeshedVersions, vendored_typeshed_versions}; -use crate::site_packages::{PythonEnvironment, SitePackagesDiscoveryError, SysPrefixPathOrigin}; +use crate::site_packages::{ + PythonEnvironment, SitePackagesDiscoveryError, SitePackagesPaths, SysPrefixPathOrigin, +}; use crate::{Program, PythonPath, SearchPathSettings}; use super::module::{Module, ModuleKind}; @@ -289,7 +291,7 @@ impl SearchPaths { virtual_env_path, error ); - vec![] + SitePackagesPaths::default() }; match PythonEnvironment::new( @@ -304,7 +306,7 @@ impl SearchPaths { } } else { tracing::debug!("No virtual environment found"); - vec![] + SitePackagesPaths::default() } } diff --git a/crates/ty_python_semantic/src/site_packages.rs b/crates/ty_python_semantic/src/site_packages.rs index 6204fff816..c01db5483e 100644 --- a/crates/ty_python_semantic/src/site_packages.rs +++ b/crates/ty_python_semantic/src/site_packages.rs @@ -14,11 +14,71 @@ use std::io; use std::num::NonZeroUsize; use std::ops::Deref; +use indexmap::IndexSet; use ruff_db::system::{System, SystemPath, SystemPathBuf}; use ruff_python_ast::PythonVersion; type SitePackagesDiscoveryResult = Result; +/// An ordered, deduplicated set of `site-packages` search paths. +/// +/// Most environments will only have one `site-packages` directory. +/// Some virtual environments created with `--system-site-packages` +/// will also have the system installation's `site-packages` packages +/// available, however. Ephemeral environments created with `uv` in +/// `uv run --with` invocations, meanwhile, "extend" a parent environment +/// (which could be another virtual environment or a system installation, +/// and which could itself have multiple `site-packages` directories). +/// +/// We use an `IndexSet` here to guard against the (very remote) +/// possibility that an environment might somehow be marked as being +/// both a `--system-site-packages` virtual environment *and* an +/// ephemeral environment that extends the system environment. If this +/// were the case, the system environment's `site-packages` directory +/// *might* be added to the `SitePackagesPaths` twice, but we wouldn't +/// want duplicates to appear in this set. +#[derive(Debug, PartialEq, Eq, Default)] +pub(crate) struct SitePackagesPaths(IndexSet); + +impl SitePackagesPaths { + pub(crate) fn len(&self) -> usize { + self.0.len() + } + + fn single(path: SystemPathBuf) -> Self { + Self(IndexSet::from([path])) + } + + fn insert(&mut self, path: SystemPathBuf) { + self.0.insert(path); + } + + fn extend(&mut self, other: Self) { + self.0.extend(other.0); + } +} + +impl FromIterator for SitePackagesPaths { + fn from_iter>(iter: T) -> Self { + Self(IndexSet::from_iter(iter)) + } +} + +impl IntoIterator for SitePackagesPaths { + type Item = SystemPathBuf; + type IntoIter = indexmap::set::IntoIter; + + fn into_iter(self) -> Self::IntoIter { + self.0.into_iter() + } +} + +impl PartialEq<&[SystemPathBuf]> for SitePackagesPaths { + fn eq(&self, other: &&[SystemPathBuf]) -> bool { + self.0.as_slice() == *other + } +} + #[derive(Debug)] pub(crate) enum PythonEnvironment { Virtual(VirtualEnvironment), @@ -35,7 +95,7 @@ impl PythonEnvironment { // Attempt to inspect as a virtual environment first // TODO(zanieb): Consider avoiding the clone here by checking for `pyvenv.cfg` ahead-of-time - match VirtualEnvironment::new(path.clone(), origin, system) { + match VirtualEnvironment::new(path.clone(), system) { Ok(venv) => Ok(Self::Virtual(venv)), // If there's not a `pyvenv.cfg` marker, attempt to inspect as a system environment // @@ -54,7 +114,7 @@ impl PythonEnvironment { pub(crate) fn site_packages_directories( &self, system: &dyn System, - ) -> SitePackagesDiscoveryResult> { + ) -> SitePackagesDiscoveryResult { match self { Self::Virtual(env) => env.site_packages_directories(system), Self::System(env) => env.site_packages_directories(system), @@ -111,12 +171,19 @@ pub(crate) struct VirtualEnvironment { /// This field will be `None` if so. version: Option, implementation: PythonImplementation, + + /// If this virtual environment was created using uv, + /// it may be an "ephemeral" virtual environment that dynamically adds the `site-packages` + /// directories of its parent environment to `sys.path` at runtime. + /// Newer versions of uv record the parent environment in the `pyvenv.cfg` file; + /// we'll want to add the `site-packages` directories of the parent environment + /// as search paths as well as the `site-packages` directories of this virtual environment. + parent_environment: Option>, } impl VirtualEnvironment { pub(crate) fn new( path: SysPrefixPath, - origin: SysPrefixPathOrigin, system: &dyn System, ) -> SitePackagesDiscoveryResult { fn pyvenv_cfg_line_number(index: usize) -> NonZeroUsize { @@ -128,12 +195,14 @@ impl VirtualEnvironment { let pyvenv_cfg = system .read_to_string(&pyvenv_cfg_path) - .map_err(|io_err| SitePackagesDiscoveryError::NoPyvenvCfgFile(origin, io_err))?; + .map_err(|io_err| SitePackagesDiscoveryError::NoPyvenvCfgFile(path.origin, io_err))?; let mut include_system_site_packages = false; let mut base_executable_home_path = None; let mut version_info_string = None; let mut implementation = PythonImplementation::Unknown; + let mut created_with_uv = false; + let mut parent_environment = None; // A `pyvenv.cfg` file *looks* like a `.ini` file, but actually isn't valid `.ini` syntax! // The Python standard-library's `site` module parses these files by splitting each line on @@ -178,6 +247,8 @@ impl VirtualEnvironment { _ => PythonImplementation::Unknown, }; } + "uv" => created_with_uv = true, + "extends-environment" => parent_environment = Some(value), _ => continue, } } @@ -196,11 +267,32 @@ impl VirtualEnvironment { let base_executable_home_path = PythonHomePath::new(base_executable_home_path, system) .map_err(|io_err| { SitePackagesDiscoveryError::PyvenvCfgParseError( - pyvenv_cfg_path, + pyvenv_cfg_path.clone(), PyvenvCfgParseErrorKind::InvalidHomeValue(io_err), ) })?; + // Since the `extends-environment` key is nonstandard, + // for now we only trust it if the virtual environment was created with `uv`. + let parent_environment = if created_with_uv { + parent_environment + .and_then(|sys_prefix| { + PythonEnvironment::new(sys_prefix, SysPrefixPathOrigin::DerivedFromPyvenvCfg, system) + .inspect_err(|err| { + tracing::warn!( + "Failed to resolve the parent environment of this ephemeral uv virtual environment \ + from the `extends-environment` value specified in the `pyvenv.cfg` file at {pyvenv_cfg_path}. \ + Imports will not be resolved correctly if they refer to packages installed into the parent \ + environment. Underlying error: {err}", + ); + }) + .ok() + }) + .map(Box::new) + } else { + None + }; + // but the `version`/`version_info` key is not read by the standard library, // and is provided under different keys depending on which virtual-environment creation tool // created the `pyvenv.cfg` file. Lenient parsing is appropriate here: @@ -218,6 +310,7 @@ impl VirtualEnvironment { include_system_site_packages, version, implementation, + parent_environment, }; tracing::trace!("Resolved metadata for virtual environment: {metadata:?}"); @@ -230,21 +323,34 @@ impl VirtualEnvironment { pub(crate) fn site_packages_directories( &self, system: &dyn System, - ) -> SitePackagesDiscoveryResult> { + ) -> SitePackagesDiscoveryResult { let VirtualEnvironment { root_path, base_executable_home_path, include_system_site_packages, implementation, version, + parent_environment, } = self; - let mut site_packages_directories = vec![site_packages_directory_from_sys_prefix( - root_path, - *version, - *implementation, - system, - )?]; + let mut site_packages_directories = SitePackagesPaths::single( + site_packages_directory_from_sys_prefix(root_path, *version, *implementation, system)?, + ); + + if let Some(parent_env_site_packages) = parent_environment.as_deref() { + match parent_env_site_packages.site_packages_directories(system) { + Ok(parent_environment_site_packages) => { + site_packages_directories.extend(parent_environment_site_packages); + } + Err(err) => { + tracing::warn!( + "Failed to resolve the site-packages directories of this ephemeral uv virtual environment's \ + parent environment. Imports will not be resolved correctly if they refer to packages installed \ + into the parent environment. Underlying error: {err}" + ); + } + } + } if *include_system_site_packages { let system_sys_prefix = @@ -261,7 +367,7 @@ impl VirtualEnvironment { system, ) { Ok(site_packages_directory) => { - site_packages_directories.push(site_packages_directory); + site_packages_directories.insert(site_packages_directory); } Err(error) => tracing::warn!( "{error}. System site-packages will not be used for module resolution." @@ -309,15 +415,16 @@ impl SystemEnvironment { pub(crate) fn site_packages_directories( &self, system: &dyn System, - ) -> SitePackagesDiscoveryResult> { + ) -> SitePackagesDiscoveryResult { let SystemEnvironment { root_path } = self; - let site_packages_directories = vec![site_packages_directory_from_sys_prefix( - root_path, - None, - PythonImplementation::Unknown, - system, - )?]; + let site_packages_directories = + SitePackagesPaths::single(site_packages_directory_from_sys_prefix( + root_path, + None, + PythonImplementation::Unknown, + system, + )?); tracing::debug!( "Resolved site-packages directories for this environment are: {site_packages_directories:?}" @@ -550,12 +657,12 @@ impl SysPrefixPath { if cfg!(target_os = "windows") { Some(Self { inner: path.to_path_buf(), - origin: SysPrefixPathOrigin::Derived, + origin: SysPrefixPathOrigin::DerivedFromPyvenvCfg, }) } else { path.parent().map(|path| Self { inner: path.to_path_buf(), - origin: SysPrefixPathOrigin::Derived, + origin: SysPrefixPathOrigin::DerivedFromPyvenvCfg, }) } } @@ -575,13 +682,22 @@ impl fmt::Display for SysPrefixPath { } } +/// Enumeration of sources a `sys.prefix` path can come from. #[derive(Debug, PartialEq, Eq, Copy, Clone)] #[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum SysPrefixPathOrigin { + /// The `sys.prefix` path came from a `--python` CLI flag PythonCliFlag, + /// The `sys.prefix` path came from the `VIRTUAL_ENV` environment variable VirtualEnvVar, + /// The `sys.prefix` path came from the `CONDA_PREFIX` environment variable CondaPrefixVar, - Derived, + /// The `sys.prefix` path was derived from a value in a `pyvenv.cfg` file: + /// either the value associated with the `home` key + /// or the value associated with the `extends-environment` key + DerivedFromPyvenvCfg, + /// A `.venv` directory was found in the current working directory, + /// and the `sys.prefix` path is the path to that virtual environment. LocalVenv, } @@ -591,7 +707,7 @@ impl SysPrefixPathOrigin { pub(crate) fn must_be_virtual_env(self) -> bool { match self { Self::LocalVenv | Self::VirtualEnvVar => true, - Self::PythonCliFlag | Self::Derived | Self::CondaPrefixVar => false, + Self::PythonCliFlag | Self::DerivedFromPyvenvCfg | Self::CondaPrefixVar => false, } } } @@ -602,7 +718,7 @@ impl Display for SysPrefixPathOrigin { Self::PythonCliFlag => f.write_str("`--python` argument"), Self::VirtualEnvVar => f.write_str("`VIRTUAL_ENV` environment variable"), Self::CondaPrefixVar => f.write_str("`CONDA_PREFIX` environment variable"), - Self::Derived => f.write_str("derived `sys.prefix` path"), + Self::DerivedFromPyvenvCfg => f.write_str("derived `sys.prefix` path"), Self::LocalVenv => f.write_str("local virtual environment"), } } @@ -692,6 +808,7 @@ mod tests { } } + #[derive(Default)] struct VirtualEnvironmentTestCase { system_site_packages: bool, pyvenv_cfg_version_field: Option<&'static str>, @@ -784,6 +901,7 @@ mod tests { pyvenv_cfg_contents.push_str(implementation_field); pyvenv_cfg_contents.push('\n'); } + // Deliberately using weird casing here to test that our pyvenv.cfg parsing is case-insensitive: if *system_site_packages { pyvenv_cfg_contents.push_str("include-system-site-packages = TRuE\n"); @@ -827,6 +945,7 @@ mod tests { env } + #[track_caller] fn assert_virtual_environment( &self, venv: &VirtualEnvironment, @@ -901,14 +1020,18 @@ mod tests { if self_venv.system_site_packages { assert_eq!( - &site_packages_directories, - &[expected_venv_site_packages, expected_system_site_packages] + site_packages_directories, + [expected_venv_site_packages, expected_system_site_packages].as_slice() ); } else { - assert_eq!(&site_packages_directories, &[expected_venv_site_packages]); + assert_eq!( + &site_packages_directories.into_iter().next().unwrap(), + &expected_venv_site_packages + ); } } + #[track_caller] fn assert_system_environment( &self, env: &SystemEnvironment, @@ -946,7 +1069,10 @@ mod tests { )) }; - assert_eq!(&site_packages_directories, &[expected_site_packages]); + assert_eq!( + site_packages_directories, + [expected_site_packages].as_slice() + ); } } @@ -1014,10 +1140,8 @@ mod tests { free_threaded: false, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: None, - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1031,10 +1155,8 @@ mod tests { free_threaded: false, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: Some("version = 3.12"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1048,10 +1170,8 @@ mod tests { free_threaded: false, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: Some("version_info = 3.12"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1065,10 +1185,8 @@ mod tests { free_threaded: false, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: Some("version_info = 3.12.0rc2"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1082,10 +1200,8 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: false, pyvenv_cfg_version_field: Some("version_info = 3.13"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1101,8 +1217,7 @@ mod tests { virtual_env: Some(VirtualEnvironmentTestCase { system_site_packages: true, pyvenv_cfg_version_field: Some("version_info = 3.13"), - command_field: None, - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); @@ -1116,10 +1231,8 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, - pyvenv_cfg_version_field: None, - command_field: None, implementation_field: Some("implementation = PyPy"), + ..VirtualEnvironmentTestCase::default() }), }; let venv = test.run().expect_venv(); @@ -1134,10 +1247,8 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, - pyvenv_cfg_version_field: None, - command_field: None, implementation_field: Some("implementation = CPython"), + ..VirtualEnvironmentTestCase::default() }), }; let venv = test.run().expect_venv(); @@ -1152,10 +1263,8 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, - pyvenv_cfg_version_field: None, - command_field: None, implementation_field: Some("implementation = GraalVM"), + ..VirtualEnvironmentTestCase::default() }), }; let venv = test.run().expect_venv(); @@ -1169,12 +1278,7 @@ mod tests { minor_version: 13, free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, - virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, - pyvenv_cfg_version_field: None, - command_field: None, - implementation_field: None, - }), + virtual_env: Some(VirtualEnvironmentTestCase::default()), }; let venv = test.run().expect_venv(); assert_eq!(venv.implementation, PythonImplementation::Unknown); @@ -1271,12 +1375,11 @@ mod tests { free_threaded: true, origin: SysPrefixPathOrigin::VirtualEnvVar, virtual_env: Some(VirtualEnvironmentTestCase { - system_site_packages: true, pyvenv_cfg_version_field: Some("version_info = 3.13"), command_field: Some( r#"command = /.pyenv/versions/3.13.3/bin/python3.13 -m venv --without-pip --prompt="python-default/3.13.3" /somewhere-else/python/virtualenvs/python-default/3.13.3"#, ), - implementation_field: None, + ..VirtualEnvironmentTestCase::default() }), }; test.run(); diff --git a/crates/ty_test/README.md b/crates/ty_test/README.md index e16b56ec49..3db1832231 100644 --- a/crates/ty_test/README.md +++ b/crates/ty_test/README.md @@ -298,7 +298,9 @@ python-version = "3.10" This configuration will apply to all tests in the same section, and all nested sections within that section. Nested sections can override configurations from their parent sections. -See [`MarkdownTestConfig`](https://github.com/astral-sh/ruff/blob/main/crates/ty_test/src/config.rs) for the full list of supported configuration options. +To enable logging in an mdtest, set `log = true` at the top level of the TOML block. +See [`MarkdownTestConfig`](https://github.com/astral-sh/ruff/blob/main/crates/ty_test/src/config.rs) +for the full list of supported configuration options. ### Specifying a custom typeshed diff --git a/crates/ty_test/src/lib.rs b/crates/ty_test/src/lib.rs index 8375bf2046..36533cf6de 100644 --- a/crates/ty_test/src/lib.rs +++ b/crates/ty_test/src/lib.rs @@ -169,7 +169,6 @@ fn run_test( let src_path = project_root.clone(); let custom_typeshed_path = test.configuration().typeshed(); - let python_path = test.configuration().python(); let python_version = test.configuration().python_version().unwrap_or_default(); let mut typeshed_files = vec![]; @@ -189,37 +188,35 @@ fn run_test( let mut full_path = embedded.full_path(&project_root); - if let Some(typeshed_path) = custom_typeshed_path { - if let Ok(relative_path) = full_path.strip_prefix(typeshed_path.join("stdlib")) { - if relative_path.as_str() == "VERSIONS" { - has_custom_versions_file = true; - } else if relative_path.extension().is_some_and(|ext| ext == "pyi") { - typeshed_files.push(relative_path.to_path_buf()); - } + if let Some(relative_path_to_custom_typeshed) = custom_typeshed_path + .and_then(|typeshed| full_path.strip_prefix(typeshed.join("stdlib")).ok()) + { + if relative_path_to_custom_typeshed.as_str() == "VERSIONS" { + has_custom_versions_file = true; + } else if relative_path_to_custom_typeshed + .extension() + .is_some_and(|ext| ext == "pyi") + { + typeshed_files.push(relative_path_to_custom_typeshed.to_path_buf()); } - } else if let Some(python_path) = python_path { - if let Ok(relative_path) = full_path.strip_prefix(python_path) { - // Construct the path to the site-packages directory - if relative_path.as_str() != "pyvenv.cfg" { - let mut new_path = SystemPathBuf::new(); - for component in full_path.components() { - let component = component.as_str(); - if component == "" { - if cfg!(target_os = "windows") { - new_path.push("Lib"); - new_path.push("site-packages"); - } else { - new_path.push("lib"); - new_path.push(format!("python{python_version}")); - new_path.push("site-packages"); - } - } else { - new_path.push(component); - } - } - full_path = new_path; - } + } else if let Some(component_index) = full_path + .components() + .position(|c| c.as_str() == "") + { + // If the path contains ``, we need to replace it with the + // actual site-packages directory based on the Python platform and version. + let mut components = full_path.components(); + let mut new_path: SystemPathBuf = + components.by_ref().take(component_index).collect(); + if cfg!(target_os = "windows") { + new_path.extend(["Lib", "site-packages"]); + } else { + new_path.push("lib"); + new_path.push(format!("python{python_version}")); + new_path.push("site-packages"); } + new_path.extend(components.skip(1)); + full_path = new_path; } db.write_file(&full_path, &embedded.code).unwrap();