def initialize(self, i, o): from poetry.semver import parse_constraint from poetry.utils.env import Env super(EnvCommand, self).initialize(i, o) # Checking compatibility of the current environment with # the python dependency specified in pyproject.toml current_env = Env.get() supported_python = self.poetry.package.python_constraint current_python = parse_constraint(".".join( str(v) for v in current_env.version_info[:3])) if not supported_python.allows(current_python): raise RuntimeError( "The current Python version ({}) is not supported by the project ({})\n" "Please activate a compatible Python version.".format( current_python, self.poetry.package.python_versions)) self._env = Env.create_venv(o, self.poetry.package.name, cwd=self.poetry.file.parent) if self._env.is_venv() and o.is_verbose(): o.writeln("Using virtualenv: <comment>{}</>".format( self._env.path))
def test_virtualenvs_with_spaces_in_their_path_work_as_expected(tmp_dir): venv_path = Path(tmp_dir) / "Virtual Env" Env.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) assert venv.run("python", "-V", shell=True).startswith("Python")
def tmp_venv(tmp_dir, request): venv_path = Path(tmp_dir) / "venv" Env.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) yield venv shutil.rmtree(str(venv.path))
def pip_install( path: Union[Path, str], environment: Env, editable: bool = False, deps: bool = False, upgrade: bool = False, ) -> Union[int, str]: path = Path(path) if isinstance(path, str) else path is_wheel = path.suffix == ".whl" # We disable version check here as we are already pinning to version available in either the # virtual environment or the virtualenv package embedded wheel. Version checks are a wasteful # network call that adds a lot of wait time when installing a lot of packages. args = [ "install", "--disable-pip-version-check", "--prefix", str(environment.path) ] if not is_wheel: args.insert(1, "--use-pep517") if upgrade: args.append("--upgrade") if not deps: args.append("--no-deps") if editable: if not path.is_dir(): raise PoetryException( "Cannot install non directory dependencies in editable mode") args.append("-e") args.append(str(path)) try: return environment.run_pip(*args) except EnvCommandError as e: if sys.version_info < (3, 7) and not is_wheel: # Under certain Python3.6 installs vendored pip wheel does not contain zip-safe # pep517 lib. In this cases we create an isolated ephemeral virtual environment. with ephemeral_environment(executable=environment.python, with_pip=True, with_setuptools=True) as env: return environment.run( *env.get_pip_command(), *args, env={ **os.environ, "PYTHONPATH": str(env.purelib) }, ) raise PoetryException(f"Failed to install {path.as_posix()}") from e
def pip_install( path: Union[Path, str], environment: Env, editable: bool = False, deps: bool = False, upgrade: bool = False, ) -> Union[int, str]: path = Path(path) if isinstance(path, str) else path args = ["install", "--prefix", str(environment.path)] if upgrade: args.append("--upgrade") if not deps: args.append("--no-deps") if editable: if not path.is_dir(): raise PoetryException( "Cannot install non directory dependencies in editable mode") args.append("-e") args.append(str(path)) if path.is_file() and path.suffix == ".whl": return environment.run_pip(*args) with ephemeral_environment(executable=environment.python, pip=True, setuptools=True) as env: return env.run( "pip", *args, )
def _display_complete_info(self, env: Env) -> None: env_python_version = ".".join(str(s) for s in env.version_info[:3]) self.line("") self.line("<b>Virtualenv</b>") listing = [ f"<info>Python</info>: <comment>{env_python_version}</>", f"<info>Implementation</info>: <comment>{env.python_implementation}</>", "<info>Path</info>: " f" <comment>{env.path if env.is_venv() else 'NA'}</>", "<info>Executable</info>: " f" <comment>{env.python if env.is_venv() else 'NA'}</>", ] if env.is_venv(): listing.append( "<info>Valid</info>: " f" <{'comment' if env.is_sane() else 'error'}>{env.is_sane()}</>" ) self.line("\n".join(listing)) self.line("") system_env = env.parent_env python = ".".join(str(v) for v in system_env.version_info[:3]) self.line("<b>System</b>") self.line("\n".join([ f"<info>Platform</info>: <comment>{env.platform}</>", f"<info>OS</info>: <comment>{env.os}</>", f"<info>Python</info>: <comment>{python}</>", f"<info>Path</info>: <comment>{system_env.path}</>", f"<info>Executable</info>: <comment>{system_env.python}</>", ]))
def _inspect_sdist_with_setup(self, sdist_dir): info = {"requires_python": None, "requires_dist": None} setup = sdist_dir / "setup.py" if not setup.exists(): return info venv = Env.create_venv(NullIO()) current_dir = os.getcwd() os.chdir(sdist_dir.as_posix()) try: venv.run("python", "setup.py", "egg_info") egg_info = list(sdist_dir.glob("**/*.egg-info"))[0] meta = pkginfo.UnpackedSDist(str(egg_info)) if meta.requires_python: info["requires_python"] = meta.requires_python if meta.requires_dist: info["requires_dist"] = list(meta.requires_dist) else: requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: info["requires_dist"] = parse_requires(f.read()) except Exception: pass os.chdir(current_dir) return info
def pip_install( path: Union[Path, str], environment: Env, editable: bool = False, deps: bool = False, upgrade: bool = False, ) -> Union[int, str]: path = Path(path) if isinstance(path, str) else path is_wheel = path.suffix == ".whl" args = ["install", "--prefix", str(environment.path)] if not is_wheel: args.insert(1, "--use-pep517") if upgrade: args.append("--upgrade") if not deps: args.append("--no-deps") if editable: if not path.is_dir(): raise PoetryException( "Cannot install non directory dependencies in editable mode") args.append("-e") args.append(str(path)) try: return environment.run_pip(*args) except EnvCommandError as e: if sys.version_info < (3, 7) and not is_wheel: # Under certain Python3.6 installs vendored pip wheel does not contain zip-safe # pep517 lib. In this cases we create an isolated ephemeral virtual environment. with ephemeral_environment(executable=environment.python, pip=True, setuptools=True) as env: return environment.run( env._bin("pip"), *args, env={ **os.environ, "PYTHONPATH": str(env.purelib) }, ) raise PoetryException(f"Failed to install {path.as_posix()}") from e
def test_env_has_symlinks_on_nix(tmp_dir): venv_path = Path(tmp_dir) / "Virtual Env" Env.build_venv(str(venv_path)) venv = VirtualEnv(venv_path) venv_available = False try: from venv import EnvBuilder venv_available = True except ImportError: pass if os.name != "nt" and venv_available: assert os.path.islink(venv.python)
def test_env_get_in_project_venv(tmp_dir, environ): if "VIRTUAL_ENV" in environ: del environ["VIRTUAL_ENV"] (Path(tmp_dir) / ".venv").mkdir() venv = Env.get(cwd=Path(tmp_dir)) assert venv.path == Path(tmp_dir) / ".venv"
def initialize(self, i, o): from poetry.utils.env import Env super(EnvCommand, self).initialize(i, o) self._env = Env.create_venv(o, self.poetry.package.name, cwd=self.poetry.file.parent) if self._env.is_venv() and o.is_verbose(): o.writeln("Using virtualenv: <comment>{}</>".format( self._env.path))
def handle(self): from poetry.layouts import layout from poetry.utils._compat import Path from poetry.utils.env import Env from poetry.vcs.git import GitConfig if self.option("src"): layout_ = layout("src") else: layout_ = layout("standard") path = Path.cwd() / Path(self.argument("path")) name = self.option("name") if not name: name = path.name if path.exists(): if list(path.glob("*")): # Directory is not empty. Aborting. raise RuntimeError("Destination <fg=yellow>{}</> " "exists and is not empty".format(path)) readme_format = "rst" config = GitConfig() author = None if config.get("user.name"): author = config["user.name"] author_email = config.get("user.email") if author_email: author += " <{}>".format(author_email) current_env = Env.get() default_python = "^{}".format(".".join( str(v) for v in current_env.version_info[:2])) layout_ = layout_( name, "0.1.0", author=author, readme_format=readme_format, python=default_python, ) layout_.create(path) self.line("Created package <info>{}</> in <fg=blue>{}</>".format( name, path.relative_to(Path.cwd())))
def pip_install( path: Path | Link, environment: Env, editable: bool = False, deps: bool = False, upgrade: bool = False, ) -> int | str: path = url_to_path(path.url) if isinstance(path, Link) else path is_wheel = path.suffix == ".whl" # We disable version check here as we are already pinning to version available in # either the virtual environment or the virtualenv package embedded wheel. Version # checks are a wasteful network call that adds a lot of wait time when installing a # lot of packages. args = [ "install", "--disable-pip-version-check", "--prefix", str(environment.path) ] if not is_wheel: args.insert(1, "--use-pep517") if upgrade: args.append("--upgrade") if not deps: args.append("--no-deps") if editable: if not path.is_dir(): raise PoetryException( "Cannot install non directory dependencies in editable mode") args.append("-e") args.append(str(path)) try: return environment.run_pip(*args) except EnvCommandError as e: raise PoetryException(f"Failed to install {path.as_posix()}") from e
def handle(self): from poetry.layouts import layout from poetry.utils._compat import Path from poetry.utils.env import Env from poetry.vcs.git import GitConfig if (Path.cwd() / "pyproject.toml").exists(): self.error("A pyproject.toml file already exists.") return 1 vcs_config = GitConfig() self.line([ "", "This command will guide you through creating your <info>pyproject.toml</> config.", "", ]) name = self.option("name") if not name: name = Path.cwd().name.lower() question = self.create_question( "Package name [<comment>{}</comment>]: ".format(name), default=name) name = self.ask(question) version = "0.1.0" question = self.create_question( "Version [<comment>{}</comment>]: ".format(version), default=version) version = self.ask(question) description = self.option("description") or "" question = self.create_question( "Description [<comment>{}</comment>]: ".format(description), default=description, ) description = self.ask(question) author = self.option("author") if not author and vcs_config and vcs_config.get("user.name"): author = vcs_config["user.name"] author_email = vcs_config.get("user.email") if author_email: author += " <{}>".format(author_email) question = self.create_question( "Author [<comment>{}</comment>, n to skip]: ".format(author), default=author) question.validator = lambda v: self._validate_author(v, author) author = self.ask(question) if not author: authors = [] else: authors = [author] license = self.option("license") or "" question = self.create_question( "License [<comment>{}</comment>]: ".format(license), default=license) question.validator = self._validate_license license = self.ask(question) current_env = Env.get() default_python = "^{}".format(".".join( str(v) for v in current_env.version_info[:2])) question = self.create_question( "Compatible Python versions [<comment>{}</comment>]: ".format( default_python), default=default_python, ) python = self.ask(question) self.line("") requirements = {} question = ("Would you like to define your dependencies" " (require) interactively?") if self.confirm(question, True): requirements = self._format_requirements( self._determine_requirements(self.option("dependency"))) dev_requirements = {} question = ("Would you like to define your dev dependencies" " (require-dev) interactively") if self.confirm(question, True): dev_requirements = self._format_requirements( self._determine_requirements(self.option("dev-dependency"))) layout_ = layout("standard")( name, version, description=description, author=authors[0] if authors else None, license=license, python=python, dependencies=requirements, dev_dependencies=dev_requirements, ) content = layout_.generate_poetry_content() if self.input.is_interactive(): self.line("<info>Generated file</info>") self.line(["", content, ""]) if not self.confirm("Do you confirm generation?", True): self.line("<error>Command aborted</error>") return 1 with (Path.cwd() / "pyproject.toml").open("w") as f: f.write(content)
def handle(self): from poetry.packages import ProjectPackage from poetry.puzzle import Solver from poetry.repositories.repository import Repository from poetry.semver import parse_constraint from poetry.utils.env import Env packages = self.argument("package") if not packages: package = self.poetry.package else: package = ProjectPackage(self.poetry.package.name, self.poetry.package.version) requirements = self._format_requirements(packages) for name, constraint in requirements.items(): dep = package.add_dependency(name, constraint) extras = [] for extra in self.option("extras"): if " " in extra: extras += [e.strip() for e in extra.split(" ")] else: extras.append(extra) for ex in extras: dep.extras.append(ex) package.python_versions = self.option("python") or ( self.poetry.package.python_versions) pool = self.poetry.pool solver = Solver(package, pool, Repository(), Repository(), self.output) ops = solver.solve() self.line("") self.line("Resolution results:") self.line("") if self.option("tree"): show_command = self.get_application().find("show") show_command.output = self.output show_command.init_styles() packages = [op.package for op in ops] repo = Repository(packages) requires = package.requires + package.dev_requires for pkg in repo.packages: for require in requires: if pkg.name == require.name: show_command.display_package_tree(pkg, repo) break return 0 env = Env.get() current_python_version = parse_constraint(".".join( str(v) for v in env.version_info)) for op in ops: pkg = op.package if self.option("install"): if not pkg.python_constraint.allows( current_python_version) or not env.is_valid_for_marker( pkg.marker): continue self.line(" - <info>{}</info> (<comment>{}</comment>)".format( pkg.name, pkg.version)) if not pkg.python_constraint.is_any(): self.line(" - python: {}".format(pkg.python_versions)) if not pkg.marker.is_any(): self.line(" - marker: {}".format(pkg.marker))
def search_for_directory( self, dependency): # type: (DirectoryDependency) -> List[Package] if dependency.supports_poetry(): from poetry.poetry import Poetry poetry = Poetry.create(dependency.full_path) pkg = poetry.package package = Package(pkg.name, pkg.version) for dep in pkg.requires: if not dep.is_optional(): package.requires.append(dep) for extra, deps in pkg.extras.items(): if extra not in package.extras: package.extras[extra] = [] for dep in deps: package.extras[extra].append(dep) package.python_versions = pkg.python_versions else: # Execute egg_info current_dir = os.getcwd() os.chdir(str(dependency.full_path)) try: cwd = dependency.full_path venv = Env.get(NullIO(), cwd=cwd) venv.run("python", "setup.py", "egg_info") except EnvCommandError: result = SetupReader.read_from_directory(dependency.full_path) if not result["name"]: # The name could not be determined # We use the dependency name result["name"] = dependency.name if not result["version"]: # The version could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the package version for {}".format( dependency.path)) package_name = result["name"] package_version = result["version"] python_requires = result["python_requires"] if python_requires is None: python_requires = "*" package_summary = "" requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items(): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" reqs = parse_requires(requires) else: os.chdir(current_dir) # Sometimes pathlib will fail on recursive # symbolic links, so we need to workaround it # and use the glob module instead. # Note that this does not happen with pathlib2 # so it's safe to use it for Python < 3.4. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(dependency.full_path), "**", "*.egg-info"), recursive=True, )) else: egg_info = next(dependency.full_path.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) package_name = meta.name package_version = meta.version package_summary = meta.summary python_requires = meta.requires_python if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) finally: os.chdir(current_dir) package = Package(package_name, package_version) if dependency.name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( "The dependency name for {} does not match the actual package's name: {}" .format(dependency.name, package.name)) package.description = package_summary for req in reqs: dep = dependency_from_pep_508(req) if dep.in_extras: for extra in dep.in_extras: if extra not in package.extras: package.extras[extra] = [] package.extras[extra].append(dep) if not dep.is_optional(): package.requires.append(dep) if python_requires: package.python_versions = python_requires package.source_type = "directory" package.source_url = dependency.path.as_posix() for extra in dependency.extras: if extra in package.extras: for dep in package.extras[extra]: dep.activate() package.requires += package.extras[extra] return [package]
def __init__( self, path, # type: Path category="main", # type: str optional=False, # type: bool base=None, # type: Path develop=True, # type: bool ): from . import dependency_from_pep_508 from .package import Package self._path = path self._base = base self._full_path = path self._develop = develop self._supports_poetry = False if self._base and not self._path.is_absolute(): self._full_path = self._base / self._path if not self._full_path.exists(): raise ValueError("Directory {} does not exist".format(self._path)) if self._full_path.is_file(): raise ValueError("{} is a file, expected a directory".format( self._path)) # Checking content to dertermine actions setup = self._full_path / "setup.py" pyproject = TomlFile(self._full_path / "pyproject.toml") if pyproject.exists(): pyproject_content = pyproject.read() self._supports_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) if not setup.exists() and not self._supports_poetry: raise ValueError( "Directory {} does not seem to be a Python package".format( self._full_path)) if self._supports_poetry: from poetry.poetry import Poetry poetry = Poetry.create(self._full_path) package = poetry.package self._package = Package(package.pretty_name, package.version) self._package.requires += package.requires self._package.dev_requires += package.dev_requires self._package.extras = package.extras self._package.python_versions = package.python_versions else: # Execute egg_info current_dir = os.getcwd() os.chdir(str(self._full_path)) try: cwd = base venv = Env.get(NullIO(), cwd=cwd) venv.run("python", "setup.py", "egg_info") except EnvCommandError: result = SetupReader.read_from_directory(self._full_path) if not result["name"]: # The name could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the package name for {}".format( path)) if not result["version"]: # The version could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the package version for {}".format( path)) package_name = result["name"] package_version = result["version"] python_requires = result["python_requires"] if python_requires is None: python_requires = "*" package_summary = "" requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items(): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" reqs = parse_requires(requires) else: os.chdir(current_dir) # Sometimes pathlib will fail on recursive # symbolic links, so we need to workaround it # and use the glob module instead. # Note that this does not happen with pathlib2 # so it's safe to use it for Python < 3.4. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(self._full_path), "**", "*.egg-info"), recursive=True, )) else: egg_info = next(self._full_path.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) package_name = meta.name package_version = meta.version package_summary = meta.summary python_requires = meta.requires_python if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) finally: os.chdir(current_dir) package = Package(package_name, package_version) package.description = package_summary for req in reqs: package.requires.append(dependency_from_pep_508(req)) if python_requires: package.python_versions = python_requires self._package = package self._package.source_type = "directory" self._package.source_url = self._path.as_posix() super(DirectoryDependency, self).__init__( self._package.name, self._package.version, category=category, optional=optional, allows_prereleases=True, )
def load(cls, env: Env) -> "InstalledRepository": """ Load installed packages. """ repo = cls() seen = set() for entry in reversed(env.sys_path): for distribution in sorted( metadata.distributions(path=[entry]), key=lambda d: str(d._path), ): name = distribution.metadata["name"] path = Path(str(distribution._path)) version = distribution.metadata["version"] package = Package(name, version, version) package.description = distribution.metadata.get("summary", "") if package.name in seen: continue try: path.relative_to(_VENDORS) except ValueError: pass else: continue seen.add(package.name) repo.add_package(package) is_standard_package = env.is_path_relative_to_lib(path) if is_standard_package: if path.name.endswith(".dist-info"): paths = cls.get_package_paths(env=env, name=package.pretty_name) if paths: is_editable_package = False for src in paths: if cls.is_vcs_package(src, env): cls.set_package_vcs_properties( package, env) break if not (is_editable_package or env.is_path_relative_to_lib(src)): is_editable_package = True else: # TODO: handle multiple source directories? if is_editable_package: package._source_type = "directory" package._source_url = paths.pop().as_posix( ) continue if cls.is_vcs_package(path, env): cls.set_package_vcs_properties(package, env) else: # If not, it's a path dependency package._source_type = "directory" package._source_url = str(path.parent) return repo
def __init__( self, path, # type: Path category="main", # type: str optional=False, # type: bool base=None, # type: Path develop=True, # type: bool ): from . import dependency_from_pep_508 from .package import Package self._path = path self._base = base self._full_path = path self._develop = develop self._supports_poetry = False if self._base and not self._path.is_absolute(): self._full_path = self._base / self._path if not self._full_path.exists(): raise ValueError("Directory {} does not exist".format(self._path)) if self._full_path.is_file(): raise ValueError("{} is a file, expected a directory".format( self._path)) # Checking content to dertermine actions setup = self._full_path / "setup.py" pyproject = TomlFile(self._full_path / "pyproject.toml") if pyproject.exists(): pyproject_content = pyproject.read() self._supports_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) if not setup.exists() and not self._supports_poetry: raise ValueError( "Directory {} does not seem to be a Python package".format( self._full_path)) if self._supports_poetry: from poetry.poetry import Poetry poetry = Poetry.create(self._full_path) package = poetry.package self._package = Package(package.pretty_name, package.version) self._package.requires += package.requires self._package.dev_requires += package.dev_requires self._package.extras = package.extras self._package.python_versions = package.python_versions else: # Execute egg_info current_dir = os.getcwd() os.chdir(str(self._full_path)) try: cwd = base venv = Env.create_venv(NullIO(), cwd=cwd) venv.run("python", "setup.py", "egg_info") finally: os.chdir(current_dir) egg_info = list(self._full_path.glob("*.egg-info"))[0] meta = pkginfo.UnpackedSDist(str(egg_info)) if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) package = Package(meta.name, meta.version) package.description = meta.summary for req in reqs: package.requires.append(dependency_from_pep_508(req)) if meta.requires_python: package.python_versions = meta.requires_python if meta.platforms: platforms = [ p for p in meta.platforms if p.lower() != "unknown" ] if platforms: package.platform = " || ".join(platforms) self._package = package self._package.source_type = "directory" self._package.source_url = self._path.as_posix() super(DirectoryDependency, self).__init__( self._package.name, self._package.version, category=category, optional=optional, allows_prereleases=True, )
def create_package_from_distribution( cls, distribution: metadata.Distribution, env: Env ) -> Package: # We first check for a direct_url.json file to determine # the type of package. path = Path(str(distribution._path)) if ( path.name.endswith(".dist-info") and path.joinpath("direct_url.json").exists() ): return cls.create_package_from_pep610(distribution) is_standard_package = env.is_path_relative_to_lib(path) source_type = None source_url = None source_reference = None source_resolved_reference = None if is_standard_package: if path.name.endswith(".dist-info"): paths = cls.get_package_paths( env=env, name=distribution.metadata["name"] ) if paths: is_editable_package = False for src in paths: if cls.is_vcs_package(src, env): ( source_type, source_url, source_reference, ) = cls.get_package_vcs_properties_from_path(src) break if not ( is_editable_package or env.is_path_relative_to_lib(src) ): is_editable_package = True else: # TODO: handle multiple source directories? if is_editable_package: source_type = "directory" source_url = paths.pop().as_posix() elif cls.is_vcs_package(path, env): ( source_type, source_url, source_reference, ) = cls.get_package_vcs_properties_from_path( env.path / "src" / canonicalize_name(distribution.metadata["name"]) ) else: # If not, it's a path dependency source_type = "directory" source_url = str(path.parent) package = Package( distribution.metadata["name"], distribution.metadata["version"], source_type=source_type, source_url=source_url, source_reference=source_reference, source_resolved_reference=source_resolved_reference, ) package.description = distribution.metadata.get("summary", "") return package
def search_for_vcs(self, dependency): # type: (VCSDependency) -> List[Package] """ Search for the specifications that match the given VCS dependency. Basically, we clone the repository in a temporary directory and get the information we need by checking out the specified reference. """ if dependency.vcs != "git": raise ValueError("Unsupported VCS dependency {}".format( dependency.vcs)) tmp_dir = Path( mkdtemp(prefix="pypoetry-git-{}".format(dependency.name))) try: git = Git() git.clone(dependency.source, tmp_dir) git.checkout(dependency.reference, tmp_dir) revision = git.rev_parse(dependency.reference, tmp_dir).strip() if dependency.tag or dependency.rev: revision = dependency.reference pyproject = TomlFile(tmp_dir / "pyproject.toml") pyproject_content = None has_poetry = False if pyproject.exists(): pyproject_content = pyproject.read() has_poetry = ("tool" in pyproject_content and "poetry" in pyproject_content["tool"]) if pyproject_content and has_poetry: # If a pyproject.toml file exists # We use it to get the information we need info = pyproject_content["tool"]["poetry"] name = info["name"] version = info["version"] package = Package(name, version, version) package.source_type = dependency.vcs package.source_url = dependency.source package.source_reference = dependency.reference for req_name, req_constraint in info["dependencies"].items(): if req_name == "python": package.python_versions = req_constraint continue package.add_dependency(req_name, req_constraint) else: # We need to use setup.py here # to figure the information we need # We need to place ourselves in the proper # folder for it to work venv = Env.get(self._io) current_dir = os.getcwd() os.chdir(tmp_dir.as_posix()) try: try: venv.run("python", "setup.py", "egg_info") except EnvCommandError: # Most likely an error with the egg_info command self.debug( "<warning>Error executing the egg_info command. Reading setup files.</warning>" ) result = SetupReader.read_from_directory(tmp_dir) if not result["name"]: result["name"] = dependency.name if not result["version"]: # The version could not be determined # so we raise an error since it is mandatory raise RuntimeError( "Unable to retrieve the version for {}".format( dependency.name)) package_name = result["name"] package_version = result["version"] python_requires = result["python_requires"] requires = "" for dep in result["install_requires"]: requires += dep + "\n" if result["extras_require"]: requires += "\n" for extra_name, deps in result["extras_require"].items( ): requires += "[{}]\n".format(extra_name) for dep in deps: requires += dep + "\n" requires += "\n" reqs = parse_requires(requires) else: # Sometimes pathlib will fail on recursive # symbolic links, so we need to workaround it # and use the glob module instead. # Note that this does not happen with pathlib2 # so it's safe to use it for Python < 3.4. if PY35: egg_info = next( Path(p) for p in glob.glob( os.path.join(str(tmp_dir), "**", "*.egg-info"), recursive=True, )) else: egg_info = next(tmp_dir.glob("**/*.egg-info")) meta = pkginfo.UnpackedSDist(str(egg_info)) package_name = meta.name package_version = meta.version python_requires = meta.requires_python if meta.requires_dist: reqs = list(meta.requires_dist) else: reqs = [] requires = egg_info / "requires.txt" if requires.exists(): with requires.open() as f: reqs = parse_requires(f.read()) package = Package(package_name, package_version) if python_requires: package.python_versions = python_requires for req in reqs: dep = dependency_from_pep_508(req) if dep.in_extras: for extra in dep.in_extras: if extra not in package.extras: package.extras[extra] = [] package.extras[extra].append(dep) package.requires.append(dep) except Exception: raise finally: os.chdir(current_dir) package.source_type = "git" package.source_url = dependency.source package.source_reference = revision except Exception: raise finally: shutil.rmtree(tmp_dir.as_posix()) if dependency.name != package.name: # For now, the dependency's name must match the actual package's name raise RuntimeError( "The dependency name for {} does not match the actual package's name: {}" .format(dependency.name, package.name)) if dependency.extras: for extra in dependency.extras: if extra in package.extras: for dep in package.extras[extra]: dep.activate() return [package]