Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add archiving option, fix exceptions, caused by multiline content in rpm spec files, remove shebangs, fix missing provides_extra field #215

Closed
wants to merge 6 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
83 changes: 70 additions & 13 deletions py2pack/__init__.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,3 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# Copyright (c) 2013, Sascha Peilicke <[email protected]>
#
# Licensed under the Apache License, Version 2.0 (the "License");
Expand Down Expand Up @@ -38,8 +35,20 @@
from py2pack.utils import (_get_archive_filelist, get_pyproject_table,
parse_pyproject, get_setuptools_scripts,
get_metadata)

from email import parser
from packaging.requirements import Requirement
import tarfile
import zipfile

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):
Expand All @@ -51,6 +60,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)
Expand All @@ -69,7 +79,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('-', '_')
Expand All @@ -86,14 +100,34 @@ 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:
js['urls'] = []
return js


def pypi_archive_file(file_path):
try:
archive = tarfile.open(file_path)
member = archive.getmember('PKG-INFO')
if member.isfile():
return pypi_text_stream(archive.extractfile(member))
except tarfile.ReadError:
archive = zipfile.ZipFile(file_path)
member = archive.getinfo('PKG-INFO')
if not member.is_dir():
return pypi_text_stream(archive.open(member))
except Exception:
pass
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!"""
Expand Down Expand Up @@ -407,6 +441,9 @@ def generate(args):

_normalize_license(data)

for i in ['license', 'source_url', 'home_page', 'summary_no_ending_dot', 'summary']:
data[i + '_singleline'] = str(data[i]).replace('\n', '')

env = _prepare_template_env(_get_template_dirs())
template = env.get_template(args.template)
result = template.render(data).encode('utf-8') # render template and encode properly
Expand All @@ -417,6 +454,22 @@ 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 []
for required_dist in requires_dist:
req = Requirement(required_dist)
if found := extra_from_req.search(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
Expand All @@ -427,21 +480,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):
Expand Down Expand Up @@ -502,7 +559,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(
Expand Down
70 changes: 23 additions & 47 deletions py2pack/templates/fedora.spec
Original file line number Diff line number Diff line change
Expand Up @@ -3,57 +3,21 @@
Name: python-%{pypi_name}
Version: {{ version }}
Release: %autorelease
Summary: {{ summary }}
Summary: {{ summary_singleline }}

# 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_singleline }}
URL: {{ home_page_singleline }}
Source: {{ source_url_sinleline|replace(version, '%{version}') }}

BuildRequires: pyproject-rpm-macros
BuildRequires: python-devel
%if %{undefined python_module}
%define python_module() python3dist(%1)
%endif
BuildRequires: fdupes

{%- set build_requires_plus_pip = ((build_requires if build_requires and build_requires is not none else []) +
['pip']) %}
{%- for req in build_requires_plus_pip |sort %}
BuildRequires: %{python_module {{ req }}}
{%- endfor %}
{%- if (install_requires and install_requires is not none) or (tests_require and tests_require is not none) %}
# SECTION test requirements
%if %{with test}
{%- if install_requires and install_requires is not none %}
{%- for req in install_requires|reject("in",build_requires)|sort %}
BuildRequires: %{python_module {{ req }}}
{%- endfor %}
{%- endif %}
{%- if tests_require and tests_require is not none %}
{%- for req in tests_require|sort|reject("in",build_requires|sort) %}
BuildRequires: %{python_module {{ req }}}
{%- endfor %}
{%- endif %}
%endif
# /SECTION
{%- endif %}
{%- if source_url.endswith('.zip') %}
BuildRequires: unzip
{%- endif %}
BuildRequires: fdupes
{%- if install_requires and install_requires is not none %}
{%- for req in install_requires|sort %}
Requires: %{python_module {{ req }}}
{%- endfor %}
{%- endif %}
{%- if extras_require and extras_require is not none %}
{%- for reqlist in extras_require.values() %}
{%- for req in reqlist %}
Suggests: %{python_module {{ req }}}
{%- endfor %}
{%- endfor %}
{%- endif %}
{%- if not has_ext_modules %}
BuildArch: noarch
{%- endif %}
Expand All @@ -67,24 +31,33 @@ BuildArch: noarch
%package -n %{python_name}
Summary: %{summary}


{%- if provides_extra and provides_extra is not none %}
{%- set provides_extra_string = ','.join(provides_extra) %}
{%- set provides_extra_nonempty = 1 %}
{%- endif %}


%description -n %{python_name} %_description

{%- if provides_extra_nonempty %}
%pyproject_extras_subpkg -n %{python_name} {{ provides_extra_string }}
{%- endif %}

%prep
%autosetup -p1 -n %{pypi_name}-%{version}


%generate_buildrequires
%pyproject_buildrequires {% if provides_extra_nonempty %}-x {{ provides_extra_string }}{% endif %}


%build
%pyproject_wheel


%install
%pyproject_install
{%- set scripts_or_console_scripts = (
(scripts|map('basename')|list if scripts and scripts is not none else []) +
(console_scripts if console_scripts and console_scripts is not none else [])) %}
#{%- for script in scripts_or_console_scripts %}
#%python_clone -a %{buildroot}%{_bindir}/{{ script }}
#{%- endfor %}
# 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
Expand All @@ -93,6 +66,7 @@ Summary: %{summary}
{%- if testsuite or test_suite %}
%if %{with test}
%check
%pyproject_check_import
{%- if has_ext_modules %}
%pytest_arch
{%- else %}
Expand All @@ -101,8 +75,10 @@ Summary: %{summary}
%endif
{%- endif %}


%files -n %{python_name} -f %{pyproject_files}


%changelog
%autochangelog

Expand Down
8 changes: 4 additions & 4 deletions py2pack/templates/mageia.spec
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@
Name: python-%{mod_name}
Version: {{ version }}
Release: %mkrel 1
Url: {{ home_page }}
Summary: {{ summary }}
License: {{ license }}
Url: {{ home_page_singleline }}
Summary: {{ summary_singleline }}
License: {{ license_singleline }}
Group: Development/Python
Source: {{ source_url|replace(version, '%{version}') }}
Source: {{ source_url_singleline|replace(version, '%{version}') }}
BuildRoot: %{_tmppath}/%{name}-%{version}-buildroot
BuildRequires: python-devel
{%- for req in requires %}
Expand Down
8 changes: 4 additions & 4 deletions py2pack/templates/opensuse-legacy.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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_singleline|default(summary_singleline, true) }}
License: {{ license_singleline }}
URL: {{ home_page_singleline }}
Source: {{ source_url_singleline|replace(version, '%{version}') }}
BuildRequires: python-setuptools
{%- if install_requires and install_requires is not none %}
{%- for req in install_requires|sort %}
Expand Down
8 changes: 4 additions & 4 deletions py2pack/templates/opensuse.spec
Original file line number Diff line number Diff line change
Expand Up @@ -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_singleline|default(summary_singleline, true) }}
License: {{ license_singleline }}
URL: {{ home_page_singleline }}
Source: {{ source_url_singleline|replace(version, '%{version}') }}
BuildRequires: python-rpm-macros
{%- set build_requires_plus_pip = ((build_requires if build_requires and build_requires is not none else []) +
['pip']) %}
Expand Down
6 changes: 6 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,19 @@ dependencies = [
"requests",
"tomli; python_version < '3.11'",
]

requires-python = ">=3.6"
dynamic = ['version']


[project.urls]
homepage = "http://github.com/openSUSE/py2pack"

[project.optional-dependencies]
service = [
"distro",
]

[project.scripts]
py2pack = "py2pack:main"

Expand Down
Loading
Loading