def build(self): # We start by building the tarball # We will use it to build the wheel sdist_builder = SdistBuilder(self._poetry) build_for_all_formats = False for p in self._package.packages: formats = p.get("format", []) if not isinstance(formats, list): formats = [formats] if formats and sdist_builder.format not in formats: build_for_all_formats = True break sdist_file = sdist_builder.build() dist_dir = self._path / "dist" if build_for_all_formats: sdist_builder = SdistBuilder(self._poetry, ignore_packages_formats=True) with temporary_directory() as tmp_dir: sdist_file = sdist_builder.build(Path(tmp_dir)) with self.unpacked_tarball(sdist_file) as tmpdir: WheelBuilder.make_in( Factory().create_poetry(tmpdir), dist_dir, original=self._poetry ) else: with self.unpacked_tarball(sdist_file) as tmpdir: WheelBuilder.make_in( Factory().create_poetry(tmpdir), dist_dir, original=self._poetry )
def _pep517_metadata(cls, path): # type (Path) -> PackageInfo """ Helper method to use PEP-517 library to build and read package metadata. :param path: Path to package source to build and read metadata for. """ info = None try: info = cls.from_setup_files(path) if info.requires_dist is not None: return info except PackageInfoError: pass with temporary_directory() as tmp_dir: # TODO: cache PEP 517 build environment corresponding to each project venv venv_dir = Path(tmp_dir) / ".venv" EnvManager.build_venv(venv_dir.as_posix()) venv = VirtualEnv(venv_dir, venv_dir) dest_dir = Path(tmp_dir) / "dist" dest_dir.mkdir() try: venv.run("python", "-m", "pip", "install", "--disable-pip-version-check", "--ignore-installed", *PEP517_META_BUILD_DEPS) venv.run( "python", "-", input_=PEP517_META_BUILD.format(source=path.as_posix(), dest=dest_dir.as_posix()), ) return cls.from_metadata(dest_dir) except EnvCommandError as e: # something went wrong while attempting pep517 metadata build # fallback to egg_info if setup.py available cls._log("PEP517 build failed: {}".format(e), level="debug") setup_py = path / "setup.py" if not setup_py.exists(): raise PackageInfoError(path) cwd = Path.cwd() os.chdir(path.as_posix()) try: venv.run("python", "setup.py", "egg_info") return cls.from_metadata(path) except EnvCommandError: raise PackageInfoError(path) finally: os.chdir(cwd.as_posix()) if info: cls._log( "Falling back to parsed setup.py file for {}".format(path), "debug") return info # if we reach here, everything has failed and all hope is lost raise PackageInfoError(path)
def _from_sdist_file(cls, path): # type: (Path) -> PackageInfo """ Helper method to parse package information from an sdist file. We attempt to first inspect the file using `pkginfo.SDist`. If this does not provide us with package requirements, we extract the source and handle it as a directory. :param path: The sdist file to parse information from. """ info = None try: info = cls._from_distribution(pkginfo.SDist(str(path))) except ValueError: # Unable to determine dependencies # We pass and go deeper pass else: if info.requires_dist is not None: # we successfully retrieved dependencies from sdist metadata return info # Still not dependencies found # So, we unpack and introspect suffix = path.suffix if suffix == ".zip": context = zipfile.ZipFile else: if suffix == ".bz2": suffixes = path.suffixes if len(suffixes) > 1 and suffixes[-2] == ".tar": suffix = ".tar.bz2" else: suffix = ".tar.gz" context = tarfile.open with temporary_directory() as tmp: tmp = Path(tmp) with context(path.as_posix()) as archive: archive.extractall(tmp.as_posix()) # a little bit of guess work to determine the directory we care about elements = list(tmp.glob("*")) if len(elements) == 1 and elements[0].is_dir(): sdist_dir = elements[0] else: sdist_dir = tmp / path.name.rstrip(suffix) if not sdist_dir.is_dir(): sdist_dir = tmp # now this is an unpacked directory we know how to deal with new_info = cls.from_directory(path=sdist_dir) if not info: return new_info return info.update(new_info)
def test_build_sdist(): with temporary_directory() as tmp_dir, cwd(os.path.join(fixtures, "complete")): filename = api.build_sdist(tmp_dir) with tarfile.open(str(os.path.join(tmp_dir, filename))) as tar: namelist = tar.getnames() assert "my-package-1.2.3/LICENSE" in namelist
def test_build_wheel_extended(): with temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "extended")): filename = api.build_wheel(tmp_dir) whl = Path(tmp_dir) / filename assert whl.exists() validate_wheel_contents(name="extended", version="0.1", path=whl.as_posix())
def test_build_wheel_with_include(): with temporary_directory() as tmp_dir, cwd(os.path.join(fixtures, "with-include")): filename = api.build_wheel(tmp_dir) validate_wheel_contents( name="with_include", version="1.2.3", path=str(os.path.join(tmp_dir, filename)), files=["entry_points.txt"], )
def test_build_sdist(): with temporary_directory() as tmp_dir, cwd(os.path.join(fixtures, "complete")): filename = api.build_sdist(tmp_dir) validate_sdist_contents( name="my-package", version="1.2.3", path=str(os.path.join(tmp_dir, filename)), files=["LICENSE"], )
def test_utils_helpers_temporary_directory_readonly_file(): with temporary_directory() as temp_dir: readonly_filename = os.path.join(temp_dir, "file.txt") with open(readonly_filename, "w+") as readonly_file: readonly_file.write("Poetry rocks!") os.chmod(str(readonly_filename), S_IREAD) assert not os.path.exists(temp_dir) assert not os.path.exists(readonly_filename)
def test_build_sdist_with_include() -> None: with temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "with-include")): filename = api.build_sdist(tmp_dir) validate_sdist_contents( name="with-include", version="1.2.3", path=str(os.path.join(tmp_dir, filename)), files=["LICENSE"], )
def test_build_wheel() -> None: with temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "complete")): filename = api.build_wheel(tmp_dir) validate_wheel_contents( name="my_package", version="1.2.3", path=str(os.path.join(tmp_dir, filename)), files=["entry_points.txt"], )
def test_build_wheel(): with temporary_directory() as tmp_dir, cwd(os.path.join(fixtures, "complete")): filename = api.build_wheel(tmp_dir) with zipfile.ZipFile(str(os.path.join(tmp_dir, filename))) as zip: namelist = zip.namelist() assert "my_package-1.2.3.dist-info/entry_points.txt" in namelist assert "my_package-1.2.3.dist-info/WHEEL" in namelist assert "my_package-1.2.3.dist-info/METADATA" in namelist
def _links_to_data(self, links: list[Link], data: PackageInfo) -> dict[str, Any]: if not links: raise PackageNotFound( f'No valid distribution links found for package: "{data.name}" version:' f' "{data.version}"') urls = defaultdict(list) files: list[dict[str, Any]] = [] for link in links: if link.is_wheel: urls["bdist_wheel"].append(link.url) elif link.filename.endswith( (".tar.gz", ".zip", ".bz2", ".xz", ".Z", ".tar")): urls["sdist"].append(link.url) file_hash = f"{link.hash_name}:{link.hash}" if link.hash else None if not link.hash or (link.hash_name is not None and link.hash_name not in ("sha256", "sha384", "sha512") and hasattr(hashlib, link.hash_name)): with temporary_directory() as temp_dir: filepath = Path(temp_dir) / link.filename self._download(link.url, str(filepath)) known_hash = (getattr(hashlib, link.hash_name)() if link.hash_name else None) required_hash = hashlib.sha256() chunksize = 4096 with filepath.open("rb") as f: while True: chunk = f.read(chunksize) if not chunk: break if known_hash: known_hash.update(chunk) required_hash.update(chunk) if not known_hash or known_hash.hexdigest() == link.hash: file_hash = f"{required_hash.name}:{required_hash.hexdigest()}" files.append({"file": link.filename, "hash": file_hash}) data.files = files info = self._get_info_from_urls(urls) data.summary = info.summary data.requires_dist = info.requires_dist data.requires_python = info.requires_python return data.asdict()
def _get_info_from_wheel(self, url: str) -> PackageInfo: from poetry.inspection.info import PackageInfo wheel_name = urllib.parse.urlparse(url).path.rsplit("/")[-1] self._log(f"Downloading wheel: {wheel_name}", level="debug") filename = os.path.basename(wheel_name) with temporary_directory() as temp_dir: filepath = Path(temp_dir) / filename self._download(url, str(filepath)) return PackageInfo.from_wheel(filepath)
def test_build_wheel_extended(): with temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "extended")): filename = api.build_wheel(tmp_dir) whl = Path(tmp_dir) / filename assert whl.exists() with zipfile.ZipFile(str(os.path.join(tmp_dir, filename))) as zip: namelist = zip.namelist() assert "extended-0.1.dist-info/RECORD" in namelist assert "extended-0.1.dist-info/WHEEL" in namelist assert "extended-0.1.dist-info/METADATA" in namelist
def test_build_editable_wheel(): pkg_dir = Path(fixtures) / "complete" with temporary_directory() as tmp_dir, cwd(pkg_dir): filename = api.build_editable(tmp_dir) wheel_pth = Path(tmp_dir) / filename validate_wheel_contents( name="my_package", version="1.2.3", path=str(wheel_pth), ) with zipfile.ZipFile(wheel_pth) as z: namelist = z.namelist() assert "my_package.pth" in namelist assert pkg_dir.as_posix() == z.read( "my_package.pth").decode().strip()
def test_prepare_metadata_for_build_wheel_with_bad_path_dep_succeeds(): with pytest.raises( ValueError) as err, temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "with_bad_path_dep")): api.prepare_metadata_for_build_wheel(tmp_dir) assert "does not exist" in str(err.value)
def test_prepare_metadata_for_build_wheel(): entry_points = """\ [console_scripts] extra-script=my_package.extra:main[time] my-2nd-script=my_package:main2 my-script=my_package:main """ wheel_data = """\ Wheel-Version: 1.0 Generator: poetry {} Root-Is-Purelib: true Tag: py3-none-any """.format(__version__) metadata = """\ Metadata-Version: 2.1 Name: my-package Version: 1.2.3 Summary: Some description. Home-page: https://python-poetry.org/ License: MIT Keywords: packaging,dependency,poetry Author: Sébastien Eustace Author-email: [email protected] Maintainer: People Everywhere Maintainer-email: [email protected] Requires-Python: >=3.6,<4.0 Classifier: License :: OSI Approved :: MIT License Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Topic :: Software Development :: Build Tools Classifier: Topic :: Software Development :: Libraries :: Python Modules Provides-Extra: time Requires-Dist: cachy[msgpack] (>=0.2.0,<0.3.0) Requires-Dist: cleo (>=0.6,<0.7) Requires-Dist: pendulum (>=1.4,<2.0); (python_version ~= "2.7" and sys_platform == "win32" or python_version in "3.4 3.5") and (extra == "time") Project-URL: Documentation, https://python-poetry.org/docs Project-URL: Issue Tracker, https://github.com/python-poetry/poetry/issues Project-URL: Repository, https://github.com/python-poetry/poetry Description-Content-Type: text/x-rst My Package ========== """ with temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "complete")): dirname = api.prepare_metadata_for_build_wheel(tmp_dir) assert "my_package-1.2.3.dist-info" == dirname dist_info = Path(tmp_dir, dirname) assert (dist_info / "entry_points.txt").exists() assert (dist_info / "WHEEL").exists() assert (dist_info / "METADATA").exists() with (dist_info / "entry_points.txt").open(encoding="utf-8") as f: assert entry_points == decode(f.read()) with (dist_info / "WHEEL").open(encoding="utf-8") as f: assert wheel_data == decode(f.read()) with (dist_info / "METADATA").open(encoding="utf-8") as f: assert metadata == decode(f.read())
def test_prepare_metadata_for_build_wheel_with_bad_path_dev_dep_succeeds( ) -> None: with temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "with_bad_path_dev_dep")): api.prepare_metadata_for_build_wheel(tmp_dir)
def test_build_wheel_with_bad_path_dep_fails(): with pytest.raises( ValueError) as err, temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "with_bad_path_dep")): api.build_wheel(tmp_dir) assert "does not exist" in str(err.value)
def test_build_wheel_with_bad_path_dev_dep_succeeds(): with temporary_directory() as tmp_dir, cwd( os.path.join(fixtures, "with_bad_path_dev_dep")): api.build_wheel(tmp_dir)