mirror of https://github.com/streamlink/streamlink
build: remove versioningit build-req from sdist
- Replace `tool.versioningit.onbuild` hook with a custom implementation which replaces the entire `streamlink._version` module (similar to before) and which additionally removes `versioningit` from the `build-system.requires` field in `pyproject.toml` and which sets a static version string in `setup.py` - Rewrite `streamlink._version` module - Add and update comments - Update docs - Add tests
This commit is contained in:
parent
80a76451f3
commit
13762836c2
|
@ -0,0 +1,81 @@
|
|||
import re
|
||||
from contextlib import contextmanager
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Generator, Generic, TypeVar, Union
|
||||
|
||||
|
||||
# noinspection PyUnusedLocal
|
||||
def onbuild(
|
||||
build_dir: Union[str, Path],
|
||||
is_source: bool,
|
||||
template_fields: Dict[str, Any],
|
||||
params: Dict[str, Any],
|
||||
):
|
||||
"""
|
||||
Remove the ``versioningit`` build-requirement from Streamlink's source distribution.
|
||||
Also set the static version string in the :mod:`streamlink._version` module when building the sdist/bdist.
|
||||
|
||||
The version string already gets set by ``versioningit`` when building, so the sdist doesn't need to have
|
||||
``versioningit`` added as a build-requirement. Previously, the generated version string was only applied
|
||||
to the :mod:`streamlink._version` module while ``versioningit`` was still set as a build-requirement.
|
||||
|
||||
This custom onbuild hook gets called via the ``tool.versioningit.onbuild`` config in ``pyproject.toml``,
|
||||
since ``versioningit`` does only support modifying one file via its default onbuild hook configuration.
|
||||
"""
|
||||
|
||||
base_dir: Path = Path(build_dir).resolve()
|
||||
pkg_dir: Path = base_dir / "src" if is_source else base_dir
|
||||
version: str = template_fields["version"]
|
||||
cmproxy: Proxy[str]
|
||||
|
||||
# Remove versioningit from ``build-system.requires`` in ``pyproject.toml``
|
||||
if is_source:
|
||||
with update_file(base_dir / "pyproject.toml") as cmproxy:
|
||||
cmproxy.set(re.sub(
|
||||
r"^(\s*)(\"versioningit\b.+?\",).*$",
|
||||
"\\1# \\2",
|
||||
cmproxy.get(),
|
||||
flags=re.MULTILINE,
|
||||
count=1,
|
||||
))
|
||||
|
||||
# Set the static version string that gets passed directly to setuptools via ``setup.py``.
|
||||
# This is much easier compared to adding the ``project.version`` field and removing "version" from ``project.dynamic``
|
||||
# in ``pyproject.toml``.
|
||||
if is_source:
|
||||
with update_file(base_dir / "setup.py") as cmproxy:
|
||||
cmproxy.set(re.sub(
|
||||
r"^(\s*)# (version=\"\",).*$",
|
||||
f"\\1version=\"{version}\",",
|
||||
cmproxy.get(),
|
||||
flags=re.MULTILINE,
|
||||
count=1,
|
||||
))
|
||||
|
||||
# Overwrite the entire ``streamlink._version`` module
|
||||
with update_file(pkg_dir / "streamlink" / "_version.py") as cmproxy:
|
||||
cmproxy.set(f"__version__ = \"{version}\"\n")
|
||||
|
||||
|
||||
TProxyItem = TypeVar("TProxyItem")
|
||||
|
||||
|
||||
class Proxy(Generic[TProxyItem]):
|
||||
def __init__(self, data: TProxyItem):
|
||||
self._data = data
|
||||
|
||||
def get(self) -> TProxyItem:
|
||||
return self._data
|
||||
|
||||
def set(self, data: TProxyItem) -> None:
|
||||
self._data = data
|
||||
|
||||
|
||||
@contextmanager
|
||||
def update_file(file: Path) -> Generator[Proxy[str], None, None]:
|
||||
with file.open("r+", encoding="utf-8") as fh:
|
||||
proxy = Proxy(fh.read())
|
||||
yield proxy
|
||||
fh.seek(0)
|
||||
fh.write(proxy.get())
|
||||
fh.truncate()
|
|
@ -0,0 +1,66 @@
|
|||
import re
|
||||
import shutil
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from build_backend.onbuild import onbuild
|
||||
|
||||
|
||||
PROJECT_ROOT = Path(__file__).parents[1]
|
||||
|
||||
|
||||
@pytest.fixture()
|
||||
def template_fields(request: pytest.FixtureRequest) -> dict:
|
||||
template_fields = {
|
||||
"version": "1.2.3+fake",
|
||||
}
|
||||
template_fields.update(getattr(request, "param", {}))
|
||||
|
||||
return template_fields
|
||||
|
||||
|
||||
@pytest.fixture(autouse=True)
|
||||
def build(request: pytest.FixtureRequest, tmp_path: Path, template_fields: dict) -> Path:
|
||||
param = getattr(request, "param", {})
|
||||
is_source = param.get("is_source", True)
|
||||
pkg_dir = tmp_path / "src" if is_source else tmp_path
|
||||
|
||||
(pkg_dir / "streamlink").mkdir(parents=True)
|
||||
shutil.copy(PROJECT_ROOT / "pyproject.toml", tmp_path / "pyproject.toml")
|
||||
shutil.copy(PROJECT_ROOT / "setup.py", tmp_path / "setup.py")
|
||||
shutil.copy(PROJECT_ROOT / "src" / "streamlink" / "_version.py", pkg_dir / "streamlink" / "_version.py")
|
||||
|
||||
onbuild(tmp_path, is_source, template_fields, {})
|
||||
|
||||
return tmp_path
|
||||
|
||||
|
||||
@pytest.mark.parametrize("build", [pytest.param({"is_source": True}, id="is_source=True")], indirect=True)
|
||||
def test_sdist(build: Path):
|
||||
assert re.search(
|
||||
r"^(\s*)# (\"versioningit\b.+?\",).*$",
|
||||
(build / "pyproject.toml").read_text(encoding="utf-8"),
|
||||
re.MULTILINE,
|
||||
), "versioningit is not a build-requirement"
|
||||
assert re.search(
|
||||
r"^(\s*)(version=\"1\.2\.3\+fake\",).*$",
|
||||
(build / "setup.py").read_text(encoding="utf-8"),
|
||||
re.MULTILINE,
|
||||
), "setup() call defines a static version string"
|
||||
assert (build / "src" / "streamlink" / "_version.py").read_text(encoding="utf-8") \
|
||||
== "__version__ = \"1.2.3+fake\"\n", \
|
||||
"streamlink._version exports a static version string"
|
||||
|
||||
|
||||
@pytest.mark.parametrize("build", [pytest.param({"is_source": False}, id="is_source=False")], indirect=True)
|
||||
def test_bdist(build: Path):
|
||||
assert (build / "pyproject.toml").read_text(encoding="utf-8") \
|
||||
== (PROJECT_ROOT / "pyproject.toml").read_text(encoding="utf-8"), \
|
||||
"Doesn't touch pyproject.toml (irrelevant for non-sdist)"
|
||||
assert (build / "setup.py").read_text(encoding="utf-8") \
|
||||
== (PROJECT_ROOT / "setup.py").read_text(encoding="utf-8"), \
|
||||
"Doesn't touch setup.py (irrelevant for non-sdist)"
|
||||
assert (build / "streamlink" / "_version.py").read_text(encoding="utf-8") \
|
||||
== "__version__ = \"1.2.3+fake\"\n", \
|
||||
"streamlink._version exports a static version string"
|
|
@ -401,6 +401,14 @@ To install Streamlink from source you will need these dependencies.
|
|||
Since :ref:`4.0.0 <changelog:streamlink 4.0.0 (2022-05-01)>`,
|
||||
Streamlink defines a `build system <pyproject.toml_>`__ according to `PEP-517`_ / `PEP-518`_.
|
||||
|
||||
.. warning::
|
||||
|
||||
Do not build Streamlink from tarballs generated by GitHub from (tagged) git commits,
|
||||
as they are lacking the release version string.
|
||||
|
||||
Instead, install from Streamlink's signed source-distribution tarballs which are uploaded to PyPI and GitHub releases,
|
||||
or from the cloned git repository.
|
||||
|
||||
.. list-table::
|
||||
:header-rows: 1
|
||||
:class: table-custom-layout table-custom-layout-dependencies
|
||||
|
@ -421,7 +429,8 @@ Streamlink defines a `build system <pyproject.toml_>`__ according to `PEP-517`_
|
|||
* - build
|
||||
- `versioningit`_
|
||||
- At least version **2.0.0** |br|
|
||||
Used for generating the version string from git when building, or when running in an editable install
|
||||
Used for generating the version string from git when building, or when running in an editable install.
|
||||
Not needed when building wheels and installing from the source distribution.
|
||||
* - runtime
|
||||
- `certifi`_
|
||||
- Used for loading the CA bundle extracted from the Mozilla Included CA Certificate List
|
||||
|
|
|
@ -2,7 +2,9 @@
|
|||
requires = [
|
||||
"setuptools >=64",
|
||||
"wheel",
|
||||
"versioningit >=2.0.0, <3",
|
||||
# The versioningit build-requirement gets removed from the source distribution,
|
||||
# as the version string is already built into it (see the onbuild versioningit hook):
|
||||
"versioningit >=2.0.0, <3", # disabled in sdist
|
||||
]
|
||||
# setuptools build-backend override
|
||||
# https://setuptools.pypa.io/en/stable/build_meta.html
|
||||
|
@ -93,6 +95,10 @@ streamlink = [
|
|||
|
||||
# https://versioningit.readthedocs.io/en/stable/index.html
|
||||
[tool.versioningit]
|
||||
# Packagers: don't patch the `default-version` string while using the tarball built by GitHub from the tagged git commit!
|
||||
# Instead, use Streamlink's signed source distribution as package source, which has the correct version string built in.
|
||||
# This fallback `default-version` string is only used when not building from the sdist or a git repo with at least one tag.
|
||||
# See the versioningit comment at the very top of this file!
|
||||
default-version = "0.0.0+unknown"
|
||||
|
||||
[tool.versioningit.vcs]
|
||||
|
@ -107,8 +113,8 @@ distance-dirty = "{base_version}+{distance}.{vcs}{rev}.dirty"
|
|||
method = "null"
|
||||
|
||||
[tool.versioningit.onbuild]
|
||||
source-file = "src/streamlink/_version.py"
|
||||
build-file = "streamlink/_version.py"
|
||||
# When building the sdist or wheel, remove versioningit build-requirement and set the static version string
|
||||
method = { module = "build_backend.onbuild", value = "onbuild" }
|
||||
|
||||
|
||||
# https://docs.pytest.org/en/latest/reference/customize.html#configuration
|
||||
|
|
9
setup.py
9
setup.py
|
@ -74,10 +74,17 @@ data_files = [
|
|||
|
||||
if __name__ == "__main__":
|
||||
from setuptools import setup # type: ignore[import]
|
||||
from versioningit import get_cmdclasses
|
||||
|
||||
try:
|
||||
# versioningit is only required when building from git (see pyproject.toml)
|
||||
from versioningit import get_cmdclasses
|
||||
except ImportError: # pragma: no cover
|
||||
def get_cmdclasses(): # type: ignore
|
||||
return {}
|
||||
|
||||
setup(
|
||||
cmdclass=get_cmdclasses(),
|
||||
entry_points=entry_points,
|
||||
data_files=data_files,
|
||||
# version="", # static version string template, uncommented and substituted by versioningit's onbuild hook
|
||||
)
|
||||
|
|
|
@ -1,15 +1,15 @@
|
|||
# Always get the current version in "editable" installs
|
||||
# `pip install -e .` / `python setup.py develop`
|
||||
# This module will get replaced by versioningit when building a source distribution
|
||||
# and instead of trying to get the version string from git, a static version string will be set
|
||||
|
||||
def _get_version() -> str:
|
||||
"""
|
||||
Get the current version from git in "editable" installs
|
||||
"""
|
||||
from pathlib import Path
|
||||
from versioningit import get_version
|
||||
import streamlink
|
||||
|
||||
return get_version(
|
||||
project_dir=Path(streamlink.__file__).parents[2],
|
||||
)
|
||||
return get_version(project_dir=Path(streamlink.__file__).parents[2])
|
||||
|
||||
|
||||
# The following _get_version() call will get replaced by versioningit with a static version string when building streamlink
|
||||
# `pip install .` / `pip wheel .` / `python setup.py build` / `python setup.py bdist_wheel` / etc.
|
||||
__version__ = _get_version()
|
||||
|
|
Loading…
Reference in New Issue