137 lines
5.3 KiB
Python
137 lines
5.3 KiB
Python
|
#!/usr/bin/python3.11
|
||
|
"""
|
||
|
Copyright 2023, Georg Pfuetzenreuter
|
||
|
|
||
|
Licensed under the EUPL, Version 1.2 or - as soon they will be approved by the European Commission - subsequent versions of the EUPL (the "Licence").
|
||
|
You may not use this work except in compliance with the Licence.
|
||
|
An English copy of the Licence is shipped in a file called LICENSE along with this applications source code.
|
||
|
You may obtain copies of the Licence in any of the official languages at https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12.
|
||
|
|
||
|
---
|
||
|
|
||
|
opam2rpm - a tool to generate openSUSE RPM packages from OCAML packages on opam.ocaml.org.
|
||
|
"""
|
||
|
|
||
|
from argparse import ArgumentParser
|
||
|
import logging
|
||
|
import requests
|
||
|
from bs4 import BeautifulSoup
|
||
|
import re
|
||
|
import jinja2
|
||
|
import sys
|
||
|
|
||
|
argparser = ArgumentParser()
|
||
|
argparser.add_argument('--debug', help='Print verbose output', action='store_const', dest='loglevel', const=logging.DEBUG, default=logging.INFO)
|
||
|
argparser.add_argument('--package', help='Name of the OPAM package', required=True)
|
||
|
argparser.add_argument('--prefixed', help='Create package with "ocaml-" prefix', action='store_true', default=True)
|
||
|
argparser.add_argument('--opam-web', help='URL pointing to the OPAM web interface', default='https://opam.ocaml.org/')
|
||
|
|
||
|
args = argparser.parse_args()
|
||
|
downstream_prefixed = args.prefixed
|
||
|
|
||
|
def _fail(msg):
|
||
|
log.error(msg)
|
||
|
sys.exit(1)
|
||
|
|
||
|
def query_opam(package):
|
||
|
url = f'{args.opam_web}/packages/{package}'
|
||
|
result = requests.get(url)
|
||
|
status = result.status_code
|
||
|
log.debug(f'{url} returned {status}')
|
||
|
if status != 200:
|
||
|
_fail(f'{url} returned {status}')
|
||
|
return result.content
|
||
|
|
||
|
class parse_opam():
|
||
|
def __init__(self, content):
|
||
|
log.debug(f'Parsing {content}')
|
||
|
self.soup = BeautifulSoup(content, 'html.parser')
|
||
|
self.table = self.soup.find('table', attrs={'class': 'package-info'})
|
||
|
self.info = { element.find('th').text: [entry.find('span', {'class': 'formula-package'}).text for entry in element.findAll('li')] or element.find('td').text for element in self.table.findAll('tr') }
|
||
|
|
||
|
def get_title(self):
|
||
|
log.debug(f'Returning title')
|
||
|
return self.soup.title
|
||
|
|
||
|
def get_info(self):
|
||
|
log.debug(f'Returning info dict: {self.info}')
|
||
|
return self.info
|
||
|
|
||
|
def parse_homepage(homepage):
|
||
|
if 'github.com' in homepage:
|
||
|
repository_url = f'{homepage}.git'
|
||
|
log.debug(f'Set repository URL to {repository_url}')
|
||
|
repository_name = homepage.rsplit('/')[-1]
|
||
|
log.debug(f'Set repository name to {repository_name}')
|
||
|
# add parsing for other common homepages here
|
||
|
else:
|
||
|
_fail(f'Unsupported homepage: {homepage}')
|
||
|
|
||
|
if repository_name.startswith('ocaml-'):
|
||
|
is_prefixed = True
|
||
|
else:
|
||
|
is_prefixed = False
|
||
|
|
||
|
return repository_url, repository_name, is_prefixed
|
||
|
|
||
|
def evaluate_prefixing(upstream_prefixed):
|
||
|
if upstream_prefixed and downstream_prefixed:
|
||
|
# RPM package name == upstream repository name
|
||
|
return False
|
||
|
if upstream_prefixed and not downstream_prefixed:
|
||
|
# RPM package name = upstream repository name w/ stripped "ocaml-" prefix
|
||
|
_fail('Unsupported casting from upstream "ocaml-" prefixed package')
|
||
|
if not upstream_prefixed and downstream_prefixed:
|
||
|
# RPM package name = "ocaml-" + upstream repository name
|
||
|
return True
|
||
|
|
||
|
def parse_dependencies(dependencies):
|
||
|
buildrequires = []
|
||
|
for dependency in dependencies:
|
||
|
log.debug(f'Parsing dependency: {dependency}')
|
||
|
ocamldep = re.search('^\w+', dependency).group(0)
|
||
|
log.debug(f'Captured dependency: {ocamldep}')
|
||
|
if not ocamldep:
|
||
|
log.warning(f'Unable to parse upstream dependency {dependency}')
|
||
|
elif not ocamldep in ['ocaml', 'dune']:
|
||
|
buildrequires.append(f'ocamlfind({ocamldep})')
|
||
|
|
||
|
log.debug(f'Constructed OCAML build dependencies: {buildrequires}')
|
||
|
return buildrequires
|
||
|
|
||
|
def render(name, url, license, buildrequires, prefixed, vcs_url=None):
|
||
|
if not vcs_url:
|
||
|
vcs_url = url
|
||
|
templenv = jinja2.Environment(loader=jinja2.FileSystemLoader(searchpath='/home/georg/Work/templates'))
|
||
|
template_service = templenv.get_template('service')
|
||
|
template_spec = templenv.get_template('ultimate.spec')
|
||
|
log.debug(f'Rendering {template_spec}')
|
||
|
#if downstream_prefixing:
|
||
|
# name = f'ocaml-{name}'
|
||
|
rendered_service = template_service.render(pkgname=name, url=vcs_url)
|
||
|
if name.startswith('ocaml-') and prefixed:
|
||
|
name = name.lstrip('ocaml-')
|
||
|
rendered_spec = template_spec.render(pkgname=name, url=url, buildrequires=buildrequires, license=license, mode='dune', do_prefix=prefixed)
|
||
|
|
||
|
log.debug(f'Service: {rendered_service}')
|
||
|
log.debug(f'Spec: {rendered_spec}')
|
||
|
return rendered_service, rendered_spec
|
||
|
|
||
|
def main():
|
||
|
opam_package = parse_opam(query_opam(args.package))
|
||
|
info = opam_package.get_info()
|
||
|
repository_url, repository_name, is_prefixed = parse_homepage(info['Homepage'])
|
||
|
add_prefix = evaluate_prefixing(is_prefixed)
|
||
|
dependencies = parse_dependencies(info['Dependencies'])
|
||
|
render(repository_name, info['Homepage'], info['License'], dependencies, add_prefix, repository_url)
|
||
|
|
||
|
logging.basicConfig(format='%(asctime)s %(levelname)s - %(funcName)s: %(message)s', datefmt='%H:%M:%S')
|
||
|
log = logging.getLogger(__name__)
|
||
|
|
||
|
if __name__ == '__main__':
|
||
|
log.setLevel(args.loglevel)
|
||
|
log.debug(args)
|
||
|
main()
|
||
|
|
||
|
|