mirror of
https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git
synced 2026-10-24 00:48:53 +03:00
Since commita62c732936("core: change the behavior of gpiod_api_version()"), the libgpiod build expects the GPIOD_API_VERSION_STR external define to contain the API version string. The setup.py script in python bindings was not updated so it fails to build the library sources into the generated wheel. Use the correct define. Fixes:a62c732936("core: change the behavior of gpiod_api_version()") Signed-off-by: Bartosz Golaszewski <bartosz.golaszewski@oss.qualcomm.com>
285 lines
11 KiB
Python
285 lines
11 KiB
Python
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
# SPDX-FileCopyrightText: 2022 Bartosz Golaszewski <brgl@bgdev.pl>
|
|
|
|
from collections.abc import Callable
|
|
from logging import getLogger
|
|
from os import getenv, path, unlink
|
|
from shutil import copy, copytree, rmtree
|
|
from typing import TypeVar, cast
|
|
|
|
from setuptools import Extension, setup
|
|
from setuptools.command.build_ext import build_ext as orig_build_ext
|
|
from setuptools.command.sdist import sdist as orig_sdist
|
|
from setuptools.errors import BaseError
|
|
|
|
T = TypeVar("T", "sdist", "build_ext")
|
|
|
|
log = getLogger(__name__)
|
|
|
|
LINK_SYSTEM_LIBGPIOD = getenv("LINK_SYSTEM_LIBGPIOD") == "1"
|
|
LIBGPIOD_MINIMUM_VERSION = "2.1"
|
|
LIBGPIOD_VERSION = getenv("LIBGPIOD_VERSION")
|
|
SRC_BASE_URL = "https://mirrors.edge.kernel.org/pub/software/libs/libgpiod/"
|
|
TAR_FILENAME = "libgpiod-{version}.tar.gz"
|
|
ASC_FILENAME = "sha256sums.asc"
|
|
SHA256_CHUNK_SIZE = 2048
|
|
LICENSE_FILE = "LICENSE"
|
|
|
|
|
|
def sha256(filename: str) -> str:
|
|
"""
|
|
Return a sha256sum for a specific filename, loading the file in chunks
|
|
to avoid potentially excessive memory use.
|
|
"""
|
|
from hashlib import sha256
|
|
|
|
sha256sum = sha256()
|
|
with open(filename, "rb") as f:
|
|
for chunk in iter(lambda: f.read(SHA256_CHUNK_SIZE), b""):
|
|
sha256sum.update(chunk)
|
|
|
|
return sha256sum.hexdigest()
|
|
|
|
|
|
def find_sha256sum(asc_file: str, tar_filename: str) -> str:
|
|
"""
|
|
Search through a local copy of sha256sums.asc for a specific filename
|
|
and return the associated sha256 sum.
|
|
"""
|
|
with open(asc_file) as f:
|
|
for line in f:
|
|
_line = line.strip().split(" ")
|
|
if len(_line) == 2 and _line[1] == tar_filename:
|
|
return _line[0]
|
|
|
|
raise BaseError(f"no signature found for {tar_filename}")
|
|
|
|
|
|
def fetch_tarball(func: Callable[[T], None]) -> Callable[[T], None]:
|
|
"""
|
|
Verify the requested LIBGPIOD_VERSION tarball exists in sha256sums.asc,
|
|
fetch it from https://mirrors.edge.kernel.org/pub/software/libs/libgpiod/
|
|
and verify its sha256sum.
|
|
|
|
If the check passes, extract the tarball and copy the lib and include
|
|
dirs into our source tree.
|
|
"""
|
|
|
|
# If no LIBGPIOD_VERSION is specified in env, just run the command
|
|
if LIBGPIOD_VERSION is None:
|
|
return func
|
|
|
|
# If LIBGPIOD_VERSION is specified, apply the tarball wrapper
|
|
def wrapper(cmd: T) -> None:
|
|
# Just-in-time import of tarfile and urllib.request so these are
|
|
# not required for Yocto to build a vendored or linked package
|
|
import sys
|
|
import tarfile
|
|
from tempfile import TemporaryDirectory
|
|
from urllib.request import urlretrieve
|
|
|
|
from packaging.version import Version
|
|
|
|
# The "build" frontend will run setup.py twice within the same
|
|
# temporary output directory. First for "sdist" and then for "wheel"
|
|
# This would cause the build to fail with dirty "lib" and "include"
|
|
# directories.
|
|
# If the version in "libgpiod-version.txt" already matches our
|
|
# requested tarball, then skip the fetch altogether.
|
|
try:
|
|
if open("libgpiod-version.txt").read() == LIBGPIOD_VERSION:
|
|
log.info("skipping tarball fetch")
|
|
func(cmd)
|
|
return
|
|
except OSError:
|
|
pass
|
|
|
|
# Early exit for build tree with dirty lib/include dirs
|
|
for check_dir in "lib", "include":
|
|
if path.isdir(f"./{check_dir}"):
|
|
raise BaseError(f"refusing to overwrite ./{check_dir}")
|
|
|
|
with TemporaryDirectory(prefix="libgpiod-") as temp_dir:
|
|
tarball_filename = TAR_FILENAME.format(version=LIBGPIOD_VERSION)
|
|
tarball_url = f"{SRC_BASE_URL}{tarball_filename}"
|
|
asc_url = f"{SRC_BASE_URL}{ASC_FILENAME}"
|
|
|
|
log.info(f"fetching: {asc_url}")
|
|
|
|
asc_filename, _ = urlretrieve(asc_url, path.join(temp_dir, ASC_FILENAME))
|
|
|
|
tarball_sha256 = find_sha256sum(asc_filename, tarball_filename)
|
|
|
|
if Version(LIBGPIOD_VERSION) < Version(LIBGPIOD_MINIMUM_VERSION):
|
|
raise BaseError(f"requires libgpiod>={LIBGPIOD_MINIMUM_VERSION}")
|
|
|
|
log.info(f"fetching: {tarball_url}")
|
|
|
|
downloaded_tarball, _ = urlretrieve(
|
|
tarball_url, path.join(temp_dir, tarball_filename)
|
|
)
|
|
|
|
log.info(f"verifying: {tarball_filename}")
|
|
if sha256(downloaded_tarball) != tarball_sha256:
|
|
raise BaseError(f"signature mismatch for {tarball_filename}")
|
|
|
|
# Unpack the downloaded tarball
|
|
log.info(f"unpacking: {tarball_filename}")
|
|
with tarfile.open(downloaded_tarball) as f:
|
|
if sys.version_info < (3, 12):
|
|
f.extractall(temp_dir)
|
|
else:
|
|
f.extractall(temp_dir, filter=tarfile.fully_trusted_filter)
|
|
|
|
# Copy the include and lib directories we need to build libgpiod
|
|
base_dir = path.join(temp_dir, f"libgpiod-{LIBGPIOD_VERSION}")
|
|
copytree(path.join(base_dir, "include"), "./include")
|
|
copytree(path.join(base_dir, "lib"), "./lib")
|
|
|
|
# Since the license file is not in the bindings directory, we cannot
|
|
# use the PEP-639 `license-files` property since the `Distribution`
|
|
# class is constructed during `setup`, parses pyproject.toml, and
|
|
# searches for the license file before we can copy files in to the
|
|
# directory*. If the file is not there, we get an error.
|
|
#
|
|
# To work around this, we manually copy the license file to the
|
|
# current directory and insert the license file into the metadata of
|
|
# the `Distribution` prior to the cmdclass building the distribution.
|
|
#
|
|
# Copying the file as `LICENSE` allows wheels built off of the sdist
|
|
# to automatically include the LICENSE, see:
|
|
# https://setuptools.pypa.io/en/latest/userguide/miscellaneous.html
|
|
#
|
|
# *Note: this is not technically true, but it would require doing
|
|
# something more complex like a custom build backend or to wrap
|
|
# `setup` or the `Distrubtion` object which get invoked/created
|
|
# multiple times and could fetch the tarball sources multiple times.
|
|
# Further, we only really care about the license file when building
|
|
# the released distribution files (sdist and wheels).
|
|
|
|
# Read the SPDX license identifier from pyproject.toml
|
|
license_file = None
|
|
with open("pyproject.toml", "rb") as f:
|
|
if sys.version_info >= (3, 11):
|
|
import tomllib
|
|
|
|
project = cast("dict[str,str]", tomllib.load(f).get("project"))
|
|
license_file = project.get("license")
|
|
else: # tomllib isn't standard, fall back to parsing by line
|
|
for line in f.readlines():
|
|
_line = line.decode()
|
|
if _line.startswith("license = "):
|
|
license_file = _line.split("=")[1].strip(' \r\n"')
|
|
break
|
|
|
|
if license_file: # Do not allow empty string or None
|
|
_path = path.join(base_dir, "LICENSES", f"{license_file}.txt")
|
|
if path.isfile(_path):
|
|
# manually set the license list for the distribution after
|
|
# the license file has been copied into the build directory.
|
|
#
|
|
# Note: that this must be in place prior to `egg_info.run`.
|
|
# For further details, see `egg_info.find_sources` and
|
|
# `manifest_maker.add_license_files`
|
|
copy(_path, LICENSE_FILE)
|
|
cmd.distribution.metadata.license_files = [LICENSE_FILE] # type: ignore[attr-defined]
|
|
|
|
# Save the libgpiod version for sdist
|
|
open("libgpiod-version.txt", "w").write(LIBGPIOD_VERSION)
|
|
|
|
# Run the command
|
|
func(cmd)
|
|
|
|
# Clean up the build directory
|
|
if path.exists(LICENSE_FILE):
|
|
unlink(LICENSE_FILE)
|
|
rmtree("./lib", ignore_errors=True)
|
|
rmtree("./include", ignore_errors=True)
|
|
unlink("libgpiod-version.txt")
|
|
|
|
return wrapper
|
|
|
|
|
|
class build_ext(orig_build_ext):
|
|
"""
|
|
Wrap build_ext to amend the module sources and settings to build
|
|
the bindings and gpiod into a combined module when a version is
|
|
specified and LINK_SYSTEM_LIBGPIOD=1 is not present in env.
|
|
|
|
run is wrapped with @fetch_tarball in order to fetch the sources
|
|
needed to build binary wheels when LIBGPIOD_VERSION is specified, eg:
|
|
|
|
LIBGPIOD_VERSION="2.0.2" python3 -m build .
|
|
"""
|
|
|
|
@fetch_tarball
|
|
def run(self) -> None:
|
|
# Try to get the gpiod version from the .txt file included in sdist
|
|
libgpiod_version: str | None
|
|
try:
|
|
libgpiod_version = open("libgpiod-version.txt").read()
|
|
except OSError:
|
|
libgpiod_version = LIBGPIOD_VERSION
|
|
|
|
if libgpiod_version and not LINK_SYSTEM_LIBGPIOD:
|
|
# When building the extension from an sdist with a vendored
|
|
# amend gpiod._ext sources and settings accordingly.
|
|
gpiod_ext = self.ext_map["gpiod._ext"]
|
|
gpiod_ext.sources += [
|
|
"lib/chip.c",
|
|
"lib/chip-info.c",
|
|
"lib/edge-event.c",
|
|
"lib/info-event.c",
|
|
"lib/internal.c",
|
|
"lib/line-config.c",
|
|
"lib/line-info.c",
|
|
"lib/line-request.c",
|
|
"lib/line-settings.c",
|
|
"lib/misc.c",
|
|
"lib/request-config.c",
|
|
]
|
|
gpiod_ext.libraries = []
|
|
gpiod_ext.include_dirs = ["include", "lib", "gpiod/ext"]
|
|
gpiod_ext.extra_compile_args.append(
|
|
f'-DGPIOD_API_VERSION_STR="{libgpiod_version}"',
|
|
)
|
|
|
|
super().run()
|
|
|
|
|
|
class sdist(orig_sdist):
|
|
"""
|
|
Wrap sdist in order to fetch the libgpiod source files for vendoring
|
|
into a source distribution.
|
|
|
|
run is wrapped with @fetch_tarball in order to fetch the sources
|
|
needed to build binary wheels when LIBGPIOD_VERSION is specified, eg:
|
|
|
|
LIBGPIOD_VERSION="2.0.2" python3 -m build . --sdist
|
|
"""
|
|
|
|
@fetch_tarball
|
|
def run(self) -> None:
|
|
super().run()
|
|
|
|
|
|
gpiod_ext = Extension(
|
|
"gpiod._ext",
|
|
sources=[
|
|
"gpiod/ext/chip.c",
|
|
"gpiod/ext/common.c",
|
|
"gpiod/ext/line-config.c",
|
|
"gpiod/ext/line-settings.c",
|
|
"gpiod/ext/module.c",
|
|
"gpiod/ext/request.c",
|
|
],
|
|
define_macros=[("_GNU_SOURCE", "1")],
|
|
libraries=["gpiod"],
|
|
extra_compile_args=["-Wall", "-Wextra"],
|
|
)
|
|
|
|
setup(
|
|
ext_modules=[gpiod_ext],
|
|
cmdclass={"build_ext": build_ext, "sdist": sdist},
|
|
)
|