add pipgen.py
This commit is contained in:
parent
b1aa91fccc
commit
2e79dcbd34
|
@ -0,0 +1 @@
|
|||
__pycache__/
|
|
@ -0,0 +1,197 @@
|
|||
#!/usr/bin/env python3
|
||||
# PyPI ebuild autogenerator, written by ~keith
|
||||
import requests
|
||||
import mock
|
||||
import setuptools
|
||||
import subprocess
|
||||
import os
|
||||
import shutil
|
||||
import traceback
|
||||
import importlib.util
|
||||
import pprint
|
||||
import re
|
||||
import argparse
|
||||
|
||||
def get_versions(name: str) -> list:
|
||||
resp = requests.get(f'https://pypi.org/pypi/{name}/json')
|
||||
json = resp.json()
|
||||
return sorted(json['releases'].keys())
|
||||
|
||||
def get_setuptools_deps(exec_dir: str) -> list:
|
||||
old_dir = os.getcwd()
|
||||
try:
|
||||
with mock.patch.object(setuptools, 'setup') as mock_setup:
|
||||
spec = importlib.util.spec_from_file_location('setup', exec_dir.rstrip('/') + '/setup.py')
|
||||
module = importlib.util.module_from_spec(spec)
|
||||
os.chdir(exec_dir)
|
||||
spec.loader.exec_module(module)
|
||||
_, kwargs = mock_setup.call_args
|
||||
return kwargs.get('install_requires', [])
|
||||
finally:
|
||||
os.chdir(old_dir)
|
||||
|
||||
def pkg_exists(gentoo_name: str) -> bool:
|
||||
return os.path.exists('/var/db/repos/gentoo/' + gentoo_name) or os.path.exists('/var/db/repos/pipless/' + gentoo_name)
|
||||
|
||||
RE_PIP_DEP = re.compile(r'^([a-zA-Z0-9._-]+)\s*(\[[a-zA-Z0-9._,\s-]+\])?\s*(?:(<=?|!=|===?|>=?|~=)\s*([a-zA-Z0-9._*+!-]+))?(\s*,.*)?')
|
||||
def translate_pip_dep(pip_dep: str) -> str:
|
||||
match = RE_PIP_DEP.match(pip_dep)
|
||||
if not match:
|
||||
print(f'WARN: cannot parse pip_dep: {pip_dep}')
|
||||
return 'UNTRANSLATABLE: ' + pip_dep
|
||||
|
||||
name = match.group(1)
|
||||
py_useflags = match.group(2)
|
||||
version_cmp = match.group(3)
|
||||
version = match.group(4)
|
||||
comma = match.group(5)
|
||||
|
||||
if ';' in pip_dep:
|
||||
print(f'WARN: ignoring environment markers for {name}: {pip_dep.partition(";")[2]}')
|
||||
if comma:
|
||||
print(f'WARN: ignoring extra constraints for {name}: {comma.partition(";")[0]}')
|
||||
if py_useflags:
|
||||
print(f'WARN: ignoring py_useflags for {name}: {py_useflags}')
|
||||
|
||||
gentoo_name = 'dev-python/' + name.replace('_', '-').replace('.', '-')
|
||||
if not pkg_exists(gentoo_name):
|
||||
if pkg_exists(gentoo_name.lower()):
|
||||
gentoo_name = gentoo_name.lower()
|
||||
elif pkg_exists('dev-python/' + name):
|
||||
gentoo_name = 'dev-python/' + name
|
||||
elif pkg_exists('dev-python/' + name.lower()):
|
||||
gentoo_name = 'dev-python/' + name.lower()
|
||||
else:
|
||||
print(f'WARN: gentoo package not found for {name}, defaulting to {gentoo_name}')
|
||||
|
||||
if not version_cmp:
|
||||
return gentoo_name
|
||||
elif version_cmp in ('==', '==='):
|
||||
return f'={gentoo_name}-{version}'
|
||||
elif version_cmp == '~=':
|
||||
return f'>={gentoo_name}-{version}'
|
||||
else:
|
||||
return f'{version_cmp}{gentoo_name}-{version}'
|
||||
|
||||
def get_package(name: str) -> dict:
|
||||
resp = requests.get(f'https://pypi.org/pypi/{name}/json')
|
||||
json = resp.json()
|
||||
|
||||
git_url = json['info']['project_urls'].get('Source Code', json['info']['home_page']).rstrip('/') + '.git'
|
||||
|
||||
pkg_data = {
|
||||
'pypi_name': json['info']['name'],
|
||||
'version': json['info']['version'],
|
||||
'description': json['info']['summary'],
|
||||
'pypi_url': json['info']['package_url'],
|
||||
'home_url': json['info']['home_page'],
|
||||
'license': json['info']['license'],
|
||||
'git_url': git_url,
|
||||
'setuptools_deps': [],
|
||||
'dependencies': [],
|
||||
}
|
||||
|
||||
print(f"Got {pkg_data['pypi_name']} version {pkg_data['version']}")
|
||||
|
||||
try:
|
||||
print("Attempting to find dependencies...")
|
||||
os.mkdir('TEMP_WORK_DIR')
|
||||
|
||||
tarball_url = None
|
||||
for url in json['urls']:
|
||||
if url['url'].endswith('.tar.gz'):
|
||||
tarball_url = url['url']
|
||||
break
|
||||
assert tarball_url, "tarball not found"
|
||||
|
||||
print("Downloading tarball...")
|
||||
tarball_path = 'TEMP_WORK_DIR/' + tarball_url.split('/')[-1]
|
||||
with requests.get(tarball_url, stream=True) as r:
|
||||
with open(tarball_path, 'wb') as fh:
|
||||
shutil.copyfileobj(r.raw, fh)
|
||||
|
||||
print("Extracting...")
|
||||
subprocess.run(['tar', '-xzf', tarball_path, '-C', 'TEMP_WORK_DIR'])
|
||||
|
||||
print("Hooking setup.py...")
|
||||
pkg_dir = 'TEMP_WORK_DIR/' + tarball_url.split('/')[-1].rpartition('.tar')[0]
|
||||
assert os.path.exists(pkg_dir + '/setup.py'), f"extracted setup.py not found in {pkg_dir}"
|
||||
deps = get_setuptools_deps(pkg_dir)
|
||||
|
||||
pkg_data['setuptools_deps'] = deps
|
||||
except BaseException as e:
|
||||
print("Error determining package dependencies. YOU WILL HAVE TO MANUALLY SPECIFY RDEPENDS!")
|
||||
traceback.print_exception(e)
|
||||
finally:
|
||||
print("Cleaning up...")
|
||||
if os.path.exists('TEMP_WORK_DIR'):
|
||||
shutil.rmtree('TEMP_WORK_DIR')
|
||||
|
||||
for dep in pkg_data['setuptools_deps']:
|
||||
pkg_data['dependencies'].append(translate_pip_dep(dep))
|
||||
|
||||
return pkg_data
|
||||
|
||||
def fill_template(pkg_data: dict) -> str:
|
||||
ebuild = f'''
|
||||
# Ebuild for {pkg_data['pypi_name']}
|
||||
# Auto-generated by pipgen.py - TEST ME!
|
||||
EAPI=8
|
||||
|
||||
DISTUTILS_USE_PEP517=setuptools
|
||||
PYTHON_COMPAT=( python3_{{8..11}} pypy3 )
|
||||
|
||||
inherit distutils-r1
|
||||
|
||||
DESCRIPTION="{pkg_data['description']}"
|
||||
HOMEPAGE="
|
||||
{pkg_data['home_url']}
|
||||
{pkg_data['pypi_url']}
|
||||
"
|
||||
|
||||
MY_PN="{pkg_data['pypi_name']}"
|
||||
MY_P="${{MY_PN}}-${{PV}}"
|
||||
|
||||
if [[ "${{PV}}" == *9999* ]]; then
|
||||
EGIT_REPO_URI="{pkg_data['git_url']}"
|
||||
inherit git-r3
|
||||
else
|
||||
SRC_URI="mirror://pypi/${{MY_P:0:1}}/${{MY_PN}}/${{MY_P}}.tar.gz"
|
||||
KEYWORDS="~alpha ~amd64 ~arm ~arm64 ~hppa ~ia64 ~loong ~mips ~ppc ~ppc64 ~riscv ~s390 ~sparc ~x86"
|
||||
S="${{WORKDIR}}/${{MY_P}}"
|
||||
fi
|
||||
|
||||
LICENSE="{pkg_data['license']}"
|
||||
SLOT="0"
|
||||
IUSE=""
|
||||
'''
|
||||
ebuild = '\n'.join(line[1:] if line.startswith('\t') else line for line in ebuild.split('\n')) + "\n"
|
||||
ebuild += 'RDEPEND="\n'
|
||||
for pkg in pkg_data['dependencies']:
|
||||
ebuild += f"\t{pkg}[${{PYTHON_USEDEP}}]\n"
|
||||
ebuild += '"\n'
|
||||
|
||||
ebuild += "# pkg_data:\n"
|
||||
for line in pprint.pformat(pkg_data).split('\n'):
|
||||
ebuild += f"# {line}\n"
|
||||
|
||||
return ebuild
|
||||
|
||||
def __main__():
|
||||
parser = argparse.ArgumentParser(description='auto-generate ebuilds from pypi packages')
|
||||
parser.add_argument('name', help='pypi package name')
|
||||
parser.add_argument('version', nargs='?', default=None, help='package version (latest if unspecified)')
|
||||
parser.add_argument('-O', '--output', default=None, help='desired gentoo package name')
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
pkg_name = args.output or 'dev-python/' + args.name.replace('_', '-').replace('.', '-')
|
||||
if not os.path.exists(pkg_name):
|
||||
os.makedirs(pkg_name)
|
||||
|
||||
pkg_data = get_package((args.name + '/' + args.version) if args.version else args.name)
|
||||
with open(f'{pkg_name}/{pkg_name.partition("/")[0]}-{pkg_data["version"]}.ebuild', 'w') as fh:
|
||||
fh.write(fill_template(pkg_data))
|
||||
|
||||
if __name__ == '__main__':
|
||||
__main__()
|
Loading…
Reference in New Issue