Determine if package is archful by parsing the wheel name

Packages on PyPI publish wheels which contain an abi tag informing
whether a package contains extensions compiled for a specific
architecture or not.
pyp2conf reads this information and saves it in the config file.
conf2spec automatically adds `gcc` to the generated specfile in case
package is archful.
This commit is contained in:
Karolina Surma
2023-10-11 10:47:49 +02:00
committed by Karolina Surma
parent ba868ae3ce
commit f6e4b0b4ba
12 changed files with 200 additions and 9 deletions
+2 -1
View File
@@ -90,6 +90,7 @@ Configuration data is stored in a TOML file.
| source | %{pypi_source} macro with optional arguments (tarball URL can be used instead) | string |
| description | long package description | multiline string |
| extras | extra subpackages names | list of strings |
| archful | package contains compiled extensions, implies not using `BuildArch: noarch` and adding `BuildRequires: gcc` | bool
### Optional fields
@@ -106,7 +107,6 @@ Configuration data is stored in a TOML file.
| binary_files | list binary files from the package | list |
| license_files | list license files from the package | list |
| doc_files | list doc files from the package | list |
| archful | package contains compiled extensions | bool |
### Example config file generated by pyp2spec
@@ -123,6 +123,7 @@ url = "https://github.com/bachya/aionotion"
source = "%{pypi_source aionotion}"
archive_name = "aionotion"
extras = []
archful = false
```
### Spec file generated using the example config
+14
View File
@@ -16,6 +16,9 @@ from pyp2spec.utils import Pyp2specError
TEMPLATE_FILENAME = "template.spec"
ADDITIONAL_BUILD_REQUIRES_ARCHFUL = ["gcc"]
ADDITIONAL_BUILD_REQUIRES_NOARCH = []
class ConfigError(Pyp2specError):
"""Raised when a config value has got a wrong type"""
@@ -107,6 +110,16 @@ def generate_unwanted_tests(config):
return formatted_unwanteds
def list_additional_build_requires(config):
"""Returns a list of additionally defined BuildRequires,
The list differs for packages that are or aren't archful.
"""
if config.get_bool("archful"):
return ADDITIONAL_BUILD_REQUIRES_ARCHFUL
return ADDITIONAL_BUILD_REQUIRES_NOARCH
def wrap_description(config):
"""Wrap description line to 79 characters at most.
@@ -127,6 +140,7 @@ def fill_in_template(config):
spec_template = Template(f.read())
result = spec_template.render(
additional_build_requires=list_additional_build_requires(config),
archful=config.get_bool("archful"),
archive_name=config.get_string("archive_name"),
binary_files=config.get_list("binary_files"),
+28
View File
@@ -204,6 +204,32 @@ class PypiPackage:
except (KeyError, TypeError):
return self.package_data["info"]["package_url"]
def is_archful(self):
"""Determine if package is archful by checking the wheel filenames.
Wheel name consists of defined fields, one of them being an abi tag.
Example abi tags:
- click-0.2-py2.py3-none-any.whl -> "none"
- cryptography-2.2-cp34-abi3-manylinux1_x86_64.whl -> "abi3"
If the value is "none", wheel was not built for a specific architecture,
probably containing pure Python modules.
Other values indicate build for an architechture, which can mean
the presence of compiled extensions.
Packages can publish multiple wheels, the pure-Python alongside the compiled ones.
For our purposes, if we find at least one wheel with an abi tag different that "none",
we consider the package archful.
The compiled extensions bring optimizations and in Fedora,
it is generally encouraged to bring in the optional features of the packages.
"""
for entry in self.package_data["urls"]:
if entry["packagetype"] == "bdist_wheel":
abi_tag = entry["filename"].split("-")[-2]
if abi_tag != "none":
return True
# all of the found wheel names had 'none' as abi_tag
return False
def find_and_save_sdist_filename(self):
"""Save the given's package version sdist name for further processing.
@@ -348,6 +374,8 @@ def create_config_contents(
click.secho("Package is set to --archful", fg="magenta")
click.secho("You may need to specify manual_build_requires in the config file", fg="magenta")
contents["archful"] = archful
else:
contents["archful"] = pkg.is_archful()
contents["description"] = description
contents["summary"] = summary
+3 -1
View File
@@ -12,7 +12,9 @@ Source: {{source}}
BuildArch: noarch
{%- endif %}
BuildRequires: python3-devel
{% for br in additional_build_requires -%}
BuildRequires: {{br}}
{% endfor %}
# Fill in the actual package description to submit package to Fedora
%global _description %{expand:
@@ -0,0 +1,55 @@
Name: python-numpy
Version: 1.25.2
Release: %autorelease
Summary: Fundamental package for array computing in Python
# Check if the automatically generated License and its spelling is correct for Fedora
# https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/
License: BSD-3-Clause
URL: https://www.numpy.org
Source: %{pypi_source numpy}
BuildRequires: python3-devel
BuildRequires: gcc
# Fill in the actual package description to submit package to Fedora
%global _description %{expand:
This is package 'numpy' generated automatically by pyp2spec.}
%description %_description
%package -n python3-numpy
Summary: %{summary}
%description -n python3-numpy %_description
%prep
%autosetup -p1 -n numpy-%{version}
%generate_buildrequires
%pyproject_buildrequires
%build
%pyproject_wheel
%install
%pyproject_install
# For official Fedora packages, including files with '*' +auto is not allowed
# Replace it with a list of relevant Python modules/globs and list extra files in %%files
%pyproject_save_files '*' +auto
%check
%pyproject_check_import
%files -n python3-numpy -f %{pyproject_files}
%changelog
%autochangelog
File diff suppressed because one or more lines are too long
+1
View File
@@ -121,6 +121,7 @@ def test_archful_flag_is_loaded(config_dir, conf, expected):
"default_python-click.conf",
"customized_markdown-it-py.conf",
"customized_python-sphinx.conf",
"default_python-numpy.conf"
]
)
def test_default_generated_specfile(file_regression, config_dir, conf):
@@ -12,3 +12,4 @@ A asyncio-friendly library for Notion Home Monitoring devices.
'''
test_top_level = true
extras = []
archful = false
@@ -9,3 +9,4 @@ url = "https://github.com/bachya/aionotion"
source = "%{pypi_source aionotion}"
archive_name = "aionotion"
extras = []
archful = false
@@ -9,3 +9,4 @@ url = "https://palletsprojects.com/p/click/"
source = "%{pypi_source click}"
archive_name = "click"
extras = []
archful = false
@@ -0,0 +1,11 @@
archful = true
description = "This is package 'numpy' generated automatically by pyp2spec."
summary = "Fundamental package for array computing in Python"
version = "1.25.2"
pypi_version = "%{version}"
license = "BSD-3-Clause"
pypi_name = "numpy"
python_name = "python-numpy"
url = "https://www.numpy.org"
source = "%{pypi_source numpy}"
archive_name = "numpy"
+82 -6
View File
@@ -61,17 +61,16 @@ def test_config_with_customization_is_valid(betamax_session):
def test_archful_package(betamax_session):
"""Generate config for tornado which is archful"""
package = "tornado"
"""Generate config for numpy which is archful"""
package = "numpy"
config = create_config_contents(
package=package,
top_level=True,
license="Apache 2.0",
archful=True,
license="fake-string",
version="1.25.2",
session=betamax_session,
)
with open(f"tests/test_configs/customized_{package}.conf", "rb") as config_file:
with open(f"tests/test_configs/default_python-{package}.conf", "rb") as config_file:
loaded_contents = tomllib.load(config_file)
assert config["archful"] == loaded_contents["archful"]
@@ -271,3 +270,80 @@ def test_extras_detected_correctly():
}
pkg = PypiPackage("_", version="0", package_metadata=fake_pkg_data)
assert pkg.extras() == ["dev", "docs", "lint", "test"]
@pytest.mark.parametrize(
("wheel_name", "archful"), [
("sampleproject-3.0.0-py3-none-any.whl", False),
("numpy-1.26.0-cp39-cp39-win_amd64.whl", True),
("numpy-1.26.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", True),
("numpy-1.26.0-cp39-cp39-musllinux_1_1_x86_64.whl", True),
]
)
def test_archfulness_is_detected(wheel_name, archful):
fake_pkg_data = {
"urls": [
{
"packagetype": "bdist_wheel",
"filename": wheel_name,
}
]
}
pkg = PypiPackage("_", version="0", package_metadata=fake_pkg_data)
assert pkg.is_archful() is archful
def test_archfulness_is_detected_from_multiple_urls_1():
fake_pkg_data = {
"urls": [
{
"packagetype": "sdist",
"filename": "sampleproject-3.0.0.tar.gz",
},
{
"packagetype": "bdist_wheel",
"filename": "sampleproject-3.0.0-py3-none-any.whl",
},
]
}
pkg = PypiPackage("_", version="0", package_metadata=fake_pkg_data)
assert not pkg.is_archful()
def test_archfulness_is_detected_from_multiple_urls_2():
fake_pkg_data = {
"urls": [
{
"packagetype": "bdist_wheel",
"filename": "sampleproject-3.0.0-py3-none-any.whl",
},
{
"packagetype": "bdist_wheel",
"filename": "sampleproject-3.0.0-cp312-abi3-manylinux1_x86_64.whl",
},
]
}
pkg = PypiPackage("_", version="0", package_metadata=fake_pkg_data)
assert pkg.is_archful()
def test_archfulness_is_detected_from_multiple_urls_3():
fake_pkg_data = {
"urls": [
{
"packagetype": "bdist_wheel",
"filename": "sampleproject-3.0.0-cp312-abi3-manylinux1_x86_64.whl",
},
{
"packagetype": "bdist_wheel",
"filename": "sampleproject-3.0.0-cp39-cp39-win_amd64.whl",
},
{
"packagetype": "bdist_wheel",
"filename": "sampleproject-3.0.0-py3-none-any.whl",
},
]
}
pkg = PypiPackage("_", version="0", package_metadata=fake_pkg_data)
assert pkg.is_archful()