Skip to content

Commit

Permalink
build: override setuptools build backend
Browse files Browse the repository at this point in the history
- Add custom build backend which overrides certain hooks
- Include custom build backend in sdists via `MANIFEST.in`
- Remove `build>=1.0.0` workaround from `build-and-sign.sh`
- Add build backend tests, update order of tests and coverage config
  • Loading branch information
bastimeyer committed Sep 18, 2023
1 parent 97b5e18 commit 194d9bc
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 5 deletions.
1 change: 1 addition & 0 deletions .codecov.yml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ coverage:
streamlink:
threshold: 1
paths:
- "build_backend/"
- "src/streamlink/"
- "!src/streamlink/plugins/"
streamlink_cli:
Expand Down
1 change: 1 addition & 0 deletions MANIFEST.in
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ include LICENSE*
include *requirements.txt
include icon.svg

recursive-include build_backend *
recursive-include completions *
recursive-include docs *
recursive-include tests *
Expand Down
79 changes: 79 additions & 0 deletions build_backend/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import shlex
from typing import List, Optional, Tuple

from setuptools import build_meta as _build_meta

# re-export everything from `setuptools.build_meta`, so that we don't have to worry about any hooks which we don't override
# https://peps.python.org/pep-0517/
# https://peps.python.org/pep-0660/
# noinspection PyUnresolvedReferences
from setuptools.build_meta import * # noqa: F403
from setuptools.command.egg_info import egg_info as _egg_info


# ----


def get_requires_for_build_wheel( # type: ignore[no-redef]
config_settings: Optional[dict] = None,
) -> List[str]: # pragma: no cover
# Streamlink publishes three wheels on PyPI: the generic "any" wheel, the "win32" wheel and the "win-amd64" wheel:
# The Windows-wheels are special, because they include a "gui_scripts" entry point, which is used by the `pip` frontend
# to generate the "streamlinkw" launcher, which doesn't open a terminal window when launching it from a GUI application.
#
# In order to build these special Windows-wheels, the `--plat-name=...` CLI argument needs to get passed
# to the `bdist_wheel` setuptools command (provided by the `wheel` package). With the introduction of PEP517 however,
# setuptools's CLI is not used directly anymore, and instead, we have to build the wheels using the `build` package
# and the `--wheel --config-setting=--build-option=...` args, which are then forwarded to setuptools via the PEP517 hooks.
#
# Since `build==1.0.0` though, the `--config-setting` data now gets passed to all build-backend hooks involved,
# even `get_requires_for_build_wheel()`. This results in the build failing, as it executes setuptools's `egg_info` command,
# which doesn't accept the `--plat-name` argument or any other `bdist_wheel` command options.
#
# As a consequence of this, we need to override the build-backend and the `get_requires_for_build_wheel()` hook, so that
# we can filter out arguments which are not relevant to the `egg_info` command.
_filter_cmd_option_args(config_settings, "--build-option", _egg_info.user_options)

return _build_meta.get_requires_for_build_wheel(config_settings)


# ----


def _filter_cmd_option_args(
config_settings: Optional[dict],
key: str,
options: List[Tuple[str, Optional[str], str]],
) -> None:
"""Filter out args which are not recognized by a specific command and its options"""

if not config_settings or not config_settings.get(key):
return

parsed = shlex.split(config_settings[key])

result = []
val_next = False
for item in parsed:
if val_next:
val_next = False
result.append(item)
continue
for full, shorthand, *_ in options:
is_boolean = full[-1] != "="
is_shorthand = shorthand is not None and item == f"-{shorthand}"
if not is_boolean and (is_shorthand or item == f"--{full[:-1]}"):
val_next = True
result.append(item)
break
if (
is_boolean and (is_shorthand or item == f"--{full}")
or not is_boolean and item.startswith(f"--{full}")
):
result.append(item)
break

if result:
config_settings[key] = shlex.join(result)
else:
del config_settings[key]
41 changes: 41 additions & 0 deletions build_backend/test_build_backend.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import pytest
from setuptools.command.egg_info import egg_info

from build_backend import _filter_cmd_option_args


@pytest.mark.parametrize(("config_settings", "expected", "options"), [
pytest.param(
None,
None,
egg_info.user_options,
id="Empty config_settings",
),
pytest.param(
{"foo": "bar"},
{"foo": "bar"},
egg_info.user_options,
id="No --build-option key",
),
pytest.param(
{"--build-option": "--egg-base=foo/bar -e baz/qux --tag-build foo -b bar --tag-date --no-date -D"},
{"--build-option": "--egg-base=foo/bar -e baz/qux --tag-build foo -b bar --tag-date --no-date -D"},
egg_info.user_options,
id="All egg_info options",
),
pytest.param(
{"--build-option": "--foo --bar --baz"},
{},
egg_info.user_options,
id="Options unknown to egg_info",
),
pytest.param(
{"--build-option": "-p win32 --plat-name win32 --plat-name=win32"},
{},
egg_info.user_options,
id="bdist_wheel --plat-name option",
),
])
def test_filter_cmd_option_args(config_settings: dict, expected: str, options: list):
_filter_cmd_option_args(config_settings, "--build-option", options)
assert config_settings == expected
1 change: 1 addition & 0 deletions dev-requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ lxml-stubs
trio-typing
types-freezegun
types-requests
types-setuptools
types-urllib3

# scripts
Expand Down
17 changes: 15 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
[build-system]
build-backend = "setuptools.build_meta"
requires = [
"setuptools >=64",
"wheel",
"versioningit >=2.0.0, <3",
]
# setuptools build-backend override
# https://setuptools.pypa.io/en/stable/build_meta.html
backend-path = ["build_backend"]
build-backend = "__init__"


# https://peps.python.org/pep-0621/
Expand Down Expand Up @@ -110,6 +113,10 @@ build-file = "streamlink/_version.py"
# https://docs.pytest.org/en/latest/reference/customize.html#configuration
# https://docs.pytest.org/en/latest/reference/reference.html#ini-options-ref
[tool.pytest.ini_options]
testpaths = [
"build_backend",
"tests",
]
filterwarnings = [
"always",
"ignore:::pkg_resources",
Expand All @@ -121,6 +128,7 @@ filterwarnings = [
[tool.coverage.run]
branch = true
source = [
"build_backend",
"src",
"tests",
]
Expand All @@ -142,7 +150,11 @@ exclude_lines = [

# https://beta.ruff.rs/docs/configuration/
[tool.ruff]
src = ["src", "tests"]
src = [
"build_backend",
"src",
"tests",
]
target-version = "py38"
fix = false
ignore-init-module-imports = true
Expand Down Expand Up @@ -233,6 +245,7 @@ show_error_context = true
show_column_numbers = true
warn_no_return = false
files = [
"build_backend",
"src/streamlink/",
"src/streamlink_cli/",
"tests/",
Expand Down
5 changes: 2 additions & 3 deletions script/build-and-sign.sh
Original file line number Diff line number Diff line change
Expand Up @@ -54,11 +54,10 @@ build() {
log "Building Streamlink sdist and generic wheel"
python -m build --outdir "${DIST}" --sdist --wheel

# TODO: switch from `--build-option` (transitional/"escape hatch") to a custom setuptools build-backend override
# Adding `bdist_wheel` in front of `--plat-name=...` is required since `build>=1.0.0` as a workaround
# see custom build-system override in pyproject.toml
for platform in "${WHEEL_PLATFORMS[@]}"; do
log "Building Streamlink platform-specific wheel for ${platform}"
python -m build --outdir "${DIST}" --wheel --config-setting="--build-option=bdist_wheel --plat-name=${platform}"
python -m build --outdir "${DIST}" --wheel --config-setting="--build-option=--plat-name=${platform}"
done
}

Expand Down
1 change: 1 addition & 0 deletions tests/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
}

_TEST_PRIORITIES = (
"build_backend/",
"tests/testutils/",
"tests/utils/",
None,
Expand Down

0 comments on commit 194d9bc

Please sign in to comment.