diff --git a/py2pack/__init__.py b/py2pack/__init__.py index 37d39a8..bab64bf 100755 --- a/py2pack/__init__.py +++ b/py2pack/__init__.py @@ -1,6 +1,3 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -# # Copyright (c) 2013, Sascha Peilicke # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -38,8 +35,24 @@ from py2pack.utils import (_get_archive_filelist, get_pyproject_table, parse_pyproject, get_setuptools_scripts, get_metadata) - +import io from email import parser +from packaging.requirements import Requirement + +try: + import libarchive +except ModuleNotFoundError: + libarchive = None + +try: + import distro + DEFAULT_TEMPLATE = { + 'fedora': 'fedora.spec', + 'debian': 'opensuse.dsc', + 'mageia': 'mageia.spec' + }.get(distro.id(), 'opensuse.spec') +except ModuleNotFoundError: + DEFAULT_TEMPLATE = 'opensuse.spec' def replace_string(output_string, replaces): @@ -51,6 +64,7 @@ def replace_string(output_string, replaces): warnings.simplefilter('always', DeprecationWarning) + SPDX_LICENSES_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'spdx_license_map.json') with open(SPDX_LICENSES_FILE, 'r') as fp: SPDX_LICENSES = json.load(fp) @@ -69,7 +83,11 @@ def pypi_json(project, release=None): def pypi_text_file(pkg_info_path): with open(pkg_info_path, 'r') as pkg_info_file: - pkg_info_lines = parser.Parser().parse(pkg_info_file) + return pypi_text_stream(pkg_info_file) + + +def pypi_text_stream(pkg_info_stream): + pkg_info_lines = parser.Parser().parse(pkg_info_stream) pkg_info_dict = {} for key, value in pkg_info_lines.items(): key = key.lower().replace('-', '_') @@ -86,7 +104,11 @@ def pypi_text_file(pkg_info_path): def pypi_json_file(file_path): with open(file_path, 'r') as json_file: - js = json.load(json_file) + return pypi_json_stream(json_file) + + +def pypi_json_stream(json_stream): + js = json.load(json_stream) if 'info' not in js: js = {'info': js} if 'urls' not in js: @@ -94,6 +116,21 @@ def pypi_json_file(file_path): return js +def pypi_archive_file(file_path): + if libarchive is None: + return None + try: + with libarchive.file_reader(file_path) as archive: + for entry in archive: + # Check if the entry's pathname matches the target filename + if entry.pathname == 'PKG-INFO': + return pypi_text_stream(io.StringIO(entry.read().decode())) + else: + return None + except Exception: + return None + + def _get_template_dirs(): """existing directories where to search for jinja2 templates. The order is important. The first found template from the first found dir wins!""" @@ -417,6 +454,23 @@ def generate(args): outfile.close() +def fix_data(data): + extra_from_req = re.compile(r'''\bextra\s+==\s+["']([^"']+)["']''') + extras = [] + data_info = data["info"] + requires_dist = data_info["requires_dist"] or [] + provides_extra = data_info["provides_extra"] or [] + if requires_dist is not None: + for required_dist in requires_dist: + req = Requirement(required_dist) + if found := re.search(extra_from_req, str(req.marker)): + extras.append(found.group(1)) + provides_extra = list(sorted(set([*extras, *provides_extra]))) + data_info["requires_dist"] = requires_dist + data_info["provides_extra"] = provides_extra + data_info["classifiers"] = (data_info["classifiers"] or []) + + def fetch_local_data(args): localfile = args.localfile local = args.local @@ -427,21 +481,25 @@ def fetch_local_data(args): try: data = pypi_json_file(localfile) except json.decoder.JSONDecodeError: - data = pypi_text_file(localfile) + data = pypi_archive_file(localfile) + if data is None: + data = pypi_text_file(localfile) args.fetched_data = data args.version = args.fetched_data['info']['version'] - return - fetch_data(args) + fix_data(data) + else: + fetch_data(args) def fetch_data(args): - args.fetched_data = pypi_json(args.name, args.version) - urls = args.fetched_data.get('urls', []) + data = args.fetched_data = pypi_json(args.name, args.version) + urls = data.get('urls', []) if len(urls) == 0: print(f"unable to find a suitable release for {args.name}!") sys.exit(1) else: - args.version = args.fetched_data['info']['version'] # return current release number + args.version = data['info']['version'] # return current release number + fix_data(data) def newest_download_url(args): @@ -502,7 +560,7 @@ def main(): parser_generate.add_argument('--source-glob', help='source glob template') parser_generate.add_argument('--local', action='store_true', help='build from local package') parser_generate.add_argument('--localfile', default='', help='path to the local PKG-INFO or json metadata') - parser_generate.add_argument('-t', '--template', choices=file_template_list(), default='opensuse.spec', help='file template') + parser_generate.add_argument('-t', '--template', choices=file_template_list(), default=DEFAULT_TEMPLATE, help='file template') parser_generate.add_argument('-f', '--filename', help='spec filename (optional)') # TODO (toabctl): remove this is a later release parser_generate.add_argument( diff --git a/py2pack/templates/fedora.spec b/py2pack/templates/fedora.spec index 611cbe8..ee22510 100644 --- a/py2pack/templates/fedora.spec +++ b/py2pack/templates/fedora.spec @@ -3,13 +3,13 @@ Name: python-%{pypi_name} Version: {{ version }} Release: %autorelease -Summary: {{ summary }} +Summary: {{ summary|replace('\n','') }} # Check if the automatically generated License and its spelling is correct for Fedora # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ -License: {{ license }} -URL: {{ home_page }} -Source: {{ source_url|replace(version, '%{version}') }} +License: {{ license|replace('\n','') }} +URL: {{ home_page|replace('\n','') }} +Source: {{ source_url|replace(version, '%{version}')|replace('\n','') }} BuildRequires: pyproject-rpm-macros BuildRequires: python-devel diff --git a/py2pack/templates/mageia.spec b/py2pack/templates/mageia.spec index 5c2140d..ecd1450 100644 --- a/py2pack/templates/mageia.spec +++ b/py2pack/templates/mageia.spec @@ -3,11 +3,11 @@ Name: python-%{mod_name} Version: {{ version }} Release: %mkrel 1 -Url: {{ home_page }} -Summary: {{ summary }} -License: {{ license }} +Url: {{ home_page|replace('\n','') }} +Summary: {{ summary|replace('\n','') }} +License: {{ license|replace('\n','') }} Group: Development/Python -Source: {{ source_url|replace(version, '%{version}') }} +Source: {{ source_url|replace(version, '%{version}')|replace('\n','') }} BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot BuildRequires: python-devel {%- for req in requires %} diff --git a/py2pack/templates/opensuse-legacy.spec b/py2pack/templates/opensuse-legacy.spec index 349e50d..7d9c7f9 100644 --- a/py2pack/templates/opensuse-legacy.spec +++ b/py2pack/templates/opensuse-legacy.spec @@ -18,10 +18,10 @@ Name: python-{{ name }} Version: {{ version }} Release: 0 -Summary: {{ summary_no_ending_dot|default(summary, true) }} -License: {{ license }} -URL: {{ home_page }} -Source: {{ source_url|replace(version, '%{version}') }} +Summary: {{ summary_no_ending_dot|default(summary, true)|replace('\n','') }} +License: {{ license|replace('\n','') }} +URL: {{ home_page|replace('\n','') }} +Source: {{ source_url|replace(version, '%{version}')|replace('\n','') }} BuildRequires: python-setuptools {%- if install_requires and install_requires is not none %} {%- for req in install_requires|sort %} diff --git a/py2pack/templates/opensuse.spec b/py2pack/templates/opensuse.spec index 0d7286a..b5e3a38 100644 --- a/py2pack/templates/opensuse.spec +++ b/py2pack/templates/opensuse.spec @@ -19,10 +19,10 @@ Name: python-{{ name }} Version: {{ version }} Release: 0 -Summary: {{ summary_no_ending_dot|default(summary, true) }} -License: {{ license }} -URL: {{ home_page }} -Source: {{ source_url|replace(version, '%{version}') }} +Summary: {{ summary_no_ending_dot|default(summary, true)|replace('\n','') }} +License: {{ license|replace('\n','') }} +URL: {{ home_page|replace('\n','') }} +Source: {{ source_url|replace(version, '%{version}')|replace('\n','') }} BuildRequires: python-rpm-macros {%- set build_requires_plus_pip = ((build_requires if build_requires and build_requires is not none else []) + ['pip']) %} diff --git a/pyproject.toml b/pyproject.toml index fe090ff..ce3b706 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -28,6 +28,8 @@ classifiers = [ ] dependencies = [ "Jinja2", + "distro", + "libarchive-c", "backports.entry_points_selectable", "build", "setuptools", @@ -38,6 +40,7 @@ dependencies = [ "requests", "tomli; python_version < '3.11'", ] + requires-python = ">=3.6" dynamic = ['version'] @@ -45,6 +48,12 @@ dynamic = ['version'] [project.urls] homepage = "http://github.com/openSUSE/py2pack" +[project.optional-dependencies] +service = [ + "libarchive-c", + "distro", +] + [project.scripts] py2pack = "py2pack:main" diff --git a/test/examples/poetry-opensuse-augmented.spec b/test/examples/poetry-opensuse-augmented.spec index 8f12f0b..32022e6 100644 --- a/test/examples/poetry-opensuse-augmented.spec +++ b/test/examples/poetry-opensuse-augmented.spec @@ -1,7 +1,7 @@ # # spec file for package python-poetry # -# Copyright (c) 2024 SUSE LLC +# Copyright (c) __YEAR__ SUSE LLC # # All modifications and additions to the file contributed by third parties # remain the property of their copyright owners, unless otherwise agreed diff --git a/test/examples/py2pack-fedora-augmented.spec b/test/examples/py2pack-fedora-augmented.spec deleted file mode 100644 index edec47d..0000000 --- a/test/examples/py2pack-fedora-augmented.spec +++ /dev/null @@ -1,74 +0,0 @@ -%define pypi_name py2pack -%define python_name python3-%{pypi_name} -Name: python-%{pypi_name} -Version: 0.8.5 -Release: %autorelease -Summary: Generate distribution packages from PyPI - -# Check if the automatically generated License and its spelling is correct for Fedora -# https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ -License: Apache-2.0 -URL: http://github.com/openSUSE/py2pack -Source: https://files.pythonhosted.org/packages/source/p/py2pack/py2pack-%{version}.tar.gz - -BuildRequires: pyproject-rpm-macros -BuildRequires: python-devel -%if %{undefined python_module} -%define python_module() python3dist(%1) -%endif -BuildRequires: %{python_module pbr >= 1.8} -BuildRequires: %{python_module pip} -BuildRequires: %{python_module setuptools} -BuildRequires: %{python_module wheel} -# SECTION test requirements -%if %{with test} -BuildRequires: %{python_module Jinja2} -BuildRequires: %{python_module metaextract} -BuildRequires: %{python_module six} -%endif -# /SECTION -BuildRequires: fdupes -Requires: %{python_module Jinja2} -Requires: %{python_module metaextract} -Requires: %{python_module setuptools} -Requires: %{python_module six} -Suggests: %{python_module typing} -BuildArch: noarch - -# Fill in the actual package description to submit package to Fedora -%global _description %{expand: -Generate distribution packages from PyPI} - -%description %_description - -%package -n %{python_name} -Summary: %{summary} - -%description -n %{python_name} %_description - - -%prep -%autosetup -p1 -n %{pypi_name}-%{version} - -%build -%pyproject_wheel - - -%install -%pyproject_install -# -#%python_clone -a %{buildroot}%{_bindir}/py2pack -# -# 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 -%if %{with test} -%check -%pytest -%endif - -%files -n %{python_name} -f %{pyproject_files} - -%changelog -%autochangelog - diff --git a/test/examples/py2pack-opensuse-augmented.spec b/test/examples/py2pack-opensuse-augmented.spec deleted file mode 100644 index e876c16..0000000 --- a/test/examples/py2pack-opensuse-augmented.spec +++ /dev/null @@ -1,75 +0,0 @@ -# -# spec file for package python-py2pack -# -# Copyright (c) __YEAR__ SUSE LLC -# -# All modifications and additions to the file contributed by third parties -# remain the property of their copyright owners, unless otherwise agreed -# upon. The license for this file, and modifications and additions to the -# file, is the same license as for the pristine package itself (unless the -# license for the pristine package is not an Open Source License, in which -# case the license is the MIT License). An "Open Source License" is a -# license that conforms to the Open Source Definition (Version 1.9) -# published by the Open Source Initiative. - -# Please submit bugfixes or comments via https://bugs.opensuse.org/ -# - - -Name: python-py2pack -Version: 0.8.5 -Release: 0 -Summary: Generate distribution packages from PyPI -License: Apache-2.0 -URL: http://github.com/openSUSE/py2pack -Source: https://files.pythonhosted.org/packages/source/p/py2pack/py2pack-%{version}.tar.gz -BuildRequires: python-rpm-macros -BuildRequires: %{python_module pbr >= 1.8} -BuildRequires: %{python_module pip} -BuildRequires: %{python_module setuptools} -BuildRequires: %{python_module wheel} -# SECTION test requirements -BuildRequires: %{python_module Jinja2} -BuildRequires: %{python_module metaextract} -BuildRequires: %{python_module six} -# /SECTION -BuildRequires: fdupes -Requires: python-Jinja2 -Requires: python-metaextract -Requires: python-setuptools -Requires: python-six -Suggests: python-typing -BuildArch: noarch -%python_subpackages - -%description -Generate distribution packages from PyPI - -%prep -%autosetup -p1 -n py2pack-%{version} - -%build -%pyproject_wheel - -%install -%pyproject_install -%python_clone -a %{buildroot}%{_bindir}/py2pack -%python_expand %fdupes %{buildroot}%{$python_sitelib} - -%check -CHOOSE: %pytest OR %pyunittest -v OR CUSTOM - -%post -%python_install_alternative py2pack - -%postun -%python_uninstall_alternative py2pack - -%files %{python_files} -%doc AUTHORS ChangeLog README.rst -%license LICENSE -%python_alternative %{_bindir}/py2pack -%{python_sitelib}/py2pack -%{python_sitelib}/py2pack-%{version}.dist-info - -%changelog diff --git a/test/examples/sampleproject-fedora-augmented.spec b/test/examples/sampleproject-fedora-augmented.spec index 5ccce9d..42c0e98 100644 --- a/test/examples/sampleproject-fedora-augmented.spec +++ b/test/examples/sampleproject-fedora-augmented.spec @@ -7,26 +7,7 @@ Summary: A sample Python project # Check if the automatically generated License and its spelling is correct for Fedora # https://docs.fedoraproject.org/en-US/packaging-guidelines/LicensingGuidelines/ -License: Copyright (c) 2016 The Python Packaging Authority (PyPA) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - (FIXME:No SPDX) +License: Copyright (c) 2016 The Python Packaging Authority (PyPA)Permission is hereby granted, free of charge, to any person obtaining a copy ofthis software and associated documentation files (the "Software"), to deal inthe Software without restriction, including without limitation the rights touse, copy, modify, merge, publish, distribute, sublicense, and/or sell copiesof the Software, and to permit persons to whom the Software is furnished to doso, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE. (FIXME:No SPDX) URL: https://github.com/pypa/sampleproject Source: https://files.pythonhosted.org/packages/source/s/sampleproject/sampleproject-%{version}.tar.gz diff --git a/test/examples/sampleproject-opensuse-augmented.spec b/test/examples/sampleproject-opensuse-augmented.spec index 1b54385..f8efaea 100644 --- a/test/examples/sampleproject-opensuse-augmented.spec +++ b/test/examples/sampleproject-opensuse-augmented.spec @@ -20,26 +20,7 @@ Name: python-sampleproject Version: 3.0.0 Release: 0 Summary: A sample Python project -License: Copyright (c) 2016 The Python Packaging Authority (PyPA) - -Permission is hereby granted, free of charge, to any person obtaining a copy of -this software and associated documentation files (the "Software"), to deal in -the Software without restriction, including without limitation the rights to -use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies -of the Software, and to permit persons to whom the Software is furnished to do -so, subject to the following conditions: - -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. - -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. - (FIXME:No SPDX) +License: Copyright (c) 2016 The Python Packaging Authority (PyPA)Permission is hereby granted, free of charge, to any person obtaining a copy ofthis software and associated documentation files (the "Software"), to deal inthe Software without restriction, including without limitation the rights touse, copy, modify, merge, publish, distribute, sublicense, and/or sell copiesof the Software, and to permit persons to whom the Software is furnished to doso, subject to the following conditions:The above copyright notice and this permission notice shall be included in allcopies or substantial portions of the Software.THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS ORIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THEAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHERLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THESOFTWARE. (FIXME:No SPDX) URL: https://github.com/pypa/sampleproject Source: https://files.pythonhosted.org/packages/source/s/sampleproject/sampleproject-%{version}.tar.gz BuildRequires: python-rpm-macros