opam2rpm/opam2rpm.py

137 lines
5.3 KiB
Python
Raw Normal View History

#!/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()