def run(self, bin, *args, **kwargs): """ Run a command inside the Python environment. """ bin = self._bin(bin) cmd = [bin] + list(args) shell = kwargs.get("shell", False) call = kwargs.pop("call", False) input_ = kwargs.pop("input_", None) if shell: cmd = list_to_shell_command(cmd) try: if self._is_windows: kwargs["shell"] = True if input_: output = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, input=encode(input_), check=True, **kwargs).stdout elif call: return subprocess.call(cmd, stderr=subprocess.STDOUT, **kwargs) else: output = subprocess.check_output(cmd, stderr=subprocess.STDOUT, **kwargs) except CalledProcessError as e: raise EnvCommandError(e, input=input_) return decode(output)
def create_venv(self, io, name=None, executable=None, force=False ): # type: (IO, Optional[str], Optional[str], bool) -> Env if self._env is not None and not force: return self._env cwd = self._poetry.file.parent env = self.get(reload=True) if env.is_venv() and not force: # Already inside a virtualenv. return env create_venv = self._poetry.config.get("virtualenvs.create") root_venv = self._poetry.config.get("virtualenvs.in-project") venv_path = self._poetry.config.get("virtualenvs.path") if root_venv: venv_path = cwd / ".venv" elif venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" else: venv_path = Path(venv_path) if not name: name = self._poetry.package.name python_patch = ".".join([str(v) for v in sys.version_info[:3]]) python_minor = ".".join([str(v) for v in sys.version_info[:2]]) if executable: python_minor = decode( subprocess.check_output( " ".join([ executable, "-c", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:2]]))\"", ]), shell=True, ).strip()) supported_python = self._poetry.package.python_constraint if not supported_python.allows(Version.parse(python_minor)): # The currently activated or chosen Python version # is not compatible with the Python constraint specified # for the project. # If an executable has been specified, we stop there # and notify the user of the incompatibility. # Otherwise, we try to find a compatible Python version. if executable: raise NoCompatiblePythonVersionFound( self._poetry.package.python_versions, python_minor) io.write_line( "<warning>The currently activated Python version {} " "is not supported by the project ({}).\n" "Trying to find and use a compatible version.</warning> ". format(python_patch, self._poetry.package.python_versions)) for python_to_try in reversed( sorted( self._poetry.package.AVAILABLE_PYTHONS, key=lambda v: (v.startswith("3"), -len(v), v), )): if len(python_to_try) == 1: if not parse_constraint("^{}.0".format( python_to_try)).allows_any(supported_python): continue elif not supported_python.allows_all( parse_constraint(python_to_try + ".*")): continue python = "python" + python_to_try if io.is_debug(): io.write_line("<debug>Trying {}</debug>".format(python)) try: python_patch = decode( subprocess.check_output( " ".join([ python, "-c", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", ]), stderr=subprocess.STDOUT, shell=True, ).strip()) except CalledProcessError: continue if not python_patch: continue if supported_python.allows(Version.parse(python_patch)): io.write_line("Using <c1>{}</c1> ({})".format( python, python_patch)) executable = python python_minor = ".".join(python_patch.split(".")[:2]) break if not executable: raise NoCompatiblePythonVersionFound( self._poetry.package.python_versions) if root_venv: venv = venv_path else: name = self.generate_env_name(name, str(cwd)) name = "{}-py{}".format(name, python_minor.strip()) venv = venv_path / name if not venv.exists(): if create_venv is False: io.write_line("<fg=black;bg=yellow>" "Skipping virtualenv creation, " "as specified in config file." "</>") return SystemEnv(Path(sys.prefix)) io.write_line("Creating virtualenv <c1>{}</> in {}".format( name, str(venv_path))) self.build_venv(str(venv), executable=executable) else: if force: io.write_line("Recreating virtualenv <c1>{}</> in {}".format( name, str(venv))) self.remove_venv(str(venv)) self.build_venv(str(venv), executable=executable) elif io.is_very_verbose(): io.write_line( "Virtualenv <c1>{}</> already exists.".format(name)) # venv detection: # stdlib venv may symlink sys.executable, so we can't use realpath. # but others can symlink *to* the venv Python, # so we can't just use sys.executable. # So we just check every item in the symlink tree (generally <= 3) p = os.path.normcase(sys.executable) paths = [p] while os.path.islink(p): p = os.path.normcase( os.path.join(os.path.dirname(p), os.readlink(p))) paths.append(p) p_venv = os.path.normcase(str(venv)) if any(p.startswith(p_venv) for p in paths): # Running properly in the virtualenv, don't need to do anything return SystemEnv(Path(sys.prefix), self.get_base_prefix()) return VirtualEnv(venv)
def activate(self, python, io): # type: (str, IO) -> Env venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" else: venv_path = Path(venv_path) cwd = self._poetry.file.parent envs_file = TomlFile(venv_path / self.ENVS_FILE) try: python_version = Version.parse(python) python = "python{}".format(python_version.major) if python_version.precision > 1: python += ".{}".format(python_version.minor) except ValueError: # Executable in PATH or full executable path pass try: python_version = decode( subprocess.check_output( " ".join([ python, "-c", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", ]), shell=True, )) except CalledProcessError as e: raise EnvCommandError(e) python_version = Version.parse(python_version.strip()) minor = "{}.{}".format(python_version.major, python_version.minor) patch = python_version.text create = False envs = tomlkit.document() base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd)) if envs_file.exists(): envs = envs_file.read() current_env = envs.get(base_env_name) if current_env is not None: current_minor = current_env["minor"] current_patch = current_env["patch"] if current_minor == minor and current_patch != patch: # We need to recreate create = True name = "{}-py{}".format(base_env_name, minor) venv = venv_path / name # Create if needed if not venv.exists() or venv.exists() and create: in_venv = os.environ.get("VIRTUAL_ENV") is not None if in_venv or not venv.exists(): create = True if venv.exists(): # We need to check if the patch version is correct _venv = VirtualEnv(venv) current_patch = ".".join( str(v) for v in _venv.version_info[:3]) if patch != current_patch: create = True self.create_venv(io, executable=python, force=create) # Activate envs[base_env_name] = {"minor": minor, "patch": patch} envs_file.write(envs) return self.get(reload=True)
def remove(self, python): # type: (str) -> Env venv_path = self._poetry.config.get("virtualenvs.path") if venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" else: venv_path = Path(venv_path) cwd = self._poetry.file.parent envs_file = TomlFile(venv_path / self.ENVS_FILE) base_env_name = self.generate_env_name(self._poetry.package.name, str(cwd)) if python.startswith(base_env_name): venvs = self.list() for venv in venvs: if venv.path.name == python: # Exact virtualenv name if not envs_file.exists(): self.remove_venv(str(venv.path)) return venv venv_minor = ".".join( str(v) for v in venv.version_info[:2]) base_env_name = self.generate_env_name(cwd.name, str(cwd)) envs = envs_file.read() current_env = envs.get(base_env_name) if not current_env: self.remove_venv(str(venv.path)) return venv if current_env["minor"] == venv_minor: del envs[base_env_name] envs_file.write(envs) self.remove_venv(str(venv.path)) return venv raise ValueError( '<warning>Environment "{}" does not exist.</warning>'.format( python)) try: python_version = Version.parse(python) python = "python{}".format(python_version.major) if python_version.precision > 1: python += ".{}".format(python_version.minor) except ValueError: # Executable in PATH or full executable path pass try: python_version = decode( subprocess.check_output( " ".join([ python, "-c", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:3]]))\"", ]), shell=True, )) except CalledProcessError as e: raise EnvCommandError(e) python_version = Version.parse(python_version.strip()) minor = "{}.{}".format(python_version.major, python_version.minor) name = "{}-py{}".format(base_env_name, minor) venv = venv_path / name if not venv.exists(): raise ValueError( '<warning>Environment "{}" does not exist.</warning>'.format( name)) if envs_file.exists(): envs = envs_file.read() current_env = envs.get(base_env_name) if current_env is not None: current_minor = current_env["minor"] if current_minor == minor: del envs[base_env_name] envs_file.write(envs) self.remove_venv(str(venv)) return VirtualEnv(venv)
def create_venv( self, cwd, io, name=None, executable=None, force=False ): # type: (Path, IO, Optional[str], Optional[str], bool) -> Env if self._env is not None and not force: return self._env env = self.get(cwd, reload=True) if env.is_venv() and not force: # Already inside a virtualenv. return env create_venv = self._config.get("virtualenvs.create") root_venv = self._config.get("virtualenvs.in-project") venv_path = self._config.get("virtualenvs.path") if root_venv: venv_path = cwd / ".venv" elif venv_path is None: venv_path = Path(CACHE_DIR) / "virtualenvs" else: venv_path = Path(venv_path) if not name: name = cwd.name python_minor = ".".join([str(v) for v in sys.version_info[:2]]) if executable: python_minor = decode( subprocess.check_output( " ".join([ executable, "-c", "\"import sys; print('.'.join([str(s) for s in sys.version_info[:2]]))\"", ]), shell=True, )) if root_venv: venv = venv_path else: name = self.generate_env_name(name, str(cwd)) name = "{}-py{}".format(name, python_minor.strip()) venv = venv_path / name if not venv.exists(): if create_venv is False: io.write_line("<fg=black;bg=yellow>" "Skipping virtualenv creation, " "as specified in config file." "</>") return SystemEnv(Path(sys.prefix)) io.write_line("Creating virtualenv <info>{}</> in {}".format( name, str(venv_path))) self.build_venv(str(venv), executable=executable) else: if force: io.write_line("Recreating virtualenv <info>{}</> in {}".format( name, str(venv))) self.remove_venv(str(venv)) self.build_venv(str(venv), executable=executable) elif io.is_very_verbose(): io.write_line( "Virtualenv <info>{}</> already exists.".format(name)) # venv detection: # stdlib venv may symlink sys.executable, so we can't use realpath. # but others can symlink *to* the venv Python, # so we can't just use sys.executable. # So we just check every item in the symlink tree (generally <= 3) p = os.path.normcase(sys.executable) paths = [p] while os.path.islink(p): p = os.path.normcase( os.path.join(os.path.dirname(p), os.readlink(p))) paths.append(p) p_venv = os.path.normcase(str(venv)) if any(p.startswith(p_venv) for p in paths): # Running properly in the virtualenv, don't need to do anything return SystemEnv(Path(sys.prefix), self.get_base_prefix()) return VirtualEnv(venv)