虚拟环境检测器(支持venv/virtualenv)
| 761 | |
| 762 | |
| 763 | class VirtualEnvDetector(_EnvironmentDetector): |
| 764 | """虚拟环境检测器(支持venv/virtualenv)""" |
| 765 | ENV_MARKERS = ["pyvenv.cfg", "bin/activate"] |
| 766 | NAME_REGEXPS = [ |
| 767 | re.compile(r'''!=\s*x\s*]\s*;\s*then\s*VIRTUAL_ENV_PROMPT=['"]?(?P<name>[^'"\s]+)['"]?\n'''), |
| 768 | re.compile(r'''_OLD_VIRTUAL_PS1="\$\{PS1:-}"\s+PS1=['"\s]*\(['"\s]*(?P<name>.*)['"\s]*\)['"\s]*\$\{PS1:-}"'''), |
| 769 | re.compile(r'''VIRTUAL_ENV=["']?(/.*)/(?P<name>\S*)["']?\n'''), |
| 770 | ] |
| 771 | |
| 772 | def __init__(self, search_dirs: List[str]): |
| 773 | self.search_dirs = search_dirs |
| 774 | |
| 775 | def detect(self) -> List[PythonEnvironment]: |
| 776 | envs = [] |
| 777 | max_depth = 2 |
| 778 | find_bin_path = set() |
| 779 | for base_dir in self.search_dirs: |
| 780 | expanded_dir = os.path.expanduser(base_dir) |
| 781 | if not os.path.exists(expanded_dir): |
| 782 | continue |
| 783 | |
| 784 | for root, dirs, _ in os.walk(expanded_dir, topdown=True): |
| 785 | # 避免过多搜索子目录 |
| 786 | if root.replace(expanded_dir, "").count(os.sep) >= max_depth: |
| 787 | dirs.clear() |
| 788 | continue |
| 789 | |
| 790 | for tmp_dir in dirs: |
| 791 | if all(Path(root) / tmp_dir / marker for marker in self.ENV_MARKERS): |
| 792 | bin_path = "{}/bin/python".format(root) |
| 793 | if not os.path.exists(bin_path) or not os.access(bin_path, os.X_OK): |
| 794 | bin_path = "{}/bin/python3".format(root) |
| 795 | if not os.path.exists(bin_path) or not os.access(bin_path, os.X_OK): |
| 796 | continue |
| 797 | if bin_path in find_bin_path: |
| 798 | continue |
| 799 | data = self.get_env_info(root) |
| 800 | if not data: |
| 801 | continue |
| 802 | site_packages = self.get_site_packages(bin_path) |
| 803 | if not site_packages: |
| 804 | continue |
| 805 | find_bin_path.add(bin_path) |
| 806 | pe = PythonEnvironment(bin_path, data[1], "venv") |
| 807 | pe.activate_sh = "source {}/bin/activate".format(root) |
| 808 | pe.system_path = data[0] |
| 809 | pe.venv_name = data[2] |
| 810 | pe.site_packages = site_packages |
| 811 | envs.append(pe) |
| 812 | return envs |
| 813 | |
| 814 | @classmethod |
| 815 | def get_env_info(cls, path: str) -> Optional[Tuple[str, str, str]]: |
| 816 | cfg_file = Path(path) / "pyvenv.cfg" |
| 817 | atv_file = Path(path) / "bin" / "activate" |
| 818 | if not cfg_file.exists(): |
| 819 | return None |
| 820 |