summarylogtreecommitdiffstats
path: root/pip2arch.py
diff options
context:
space:
mode:
authorJiachen Yang2015-08-10 19:10:12 +0900
committerJiachen Yang2015-08-10 19:10:12 +0900
commit700e1bffd16b935a5db7c1ae973817f1bbb81d39 (patch)
tree0a19482d1129990b6ef8fa730cae176c4906de89 /pip2arch.py
downloadaur-700e1bffd16b935a5db7c1ae973817f1bbb81d39.tar.gz
takeover pkgtools
Diffstat (limited to 'pip2arch.py')
-rw-r--r--pip2arch.py255
1 files changed, 255 insertions, 0 deletions
diff --git a/pip2arch.py b/pip2arch.py
new file mode 100644
index 000000000000..6b1d9692ad8d
--- /dev/null
+++ b/pip2arch.py
@@ -0,0 +1,255 @@
+#!/usr/bin/python
+from __future__ import unicode_literals
+from __future__ import with_statement
+
+import sys
+import datetime
+import logging
+import argparse
+import re
+
+# make this script work for python2 and python3
+# the try will fail on python3
+try:
+ input = raw_input
+ from xmlrpclib import ServerProxy
+except NameError:
+ from xmlrpc.client import ServerProxy
+
+BLANK_PKGBUILD = """\
+#Automatically generated by pip2arch on {date}
+
+pkgname={pkg.outname}
+pkgver={pkg.version}
+pkgrel=1
+pkgdesc="{pkg.description}"
+url="{pkg.url}"
+depends=('{pkg.pyversion}' {depends})
+makedepends=('{pkg.distributepackage}' {makedepends})
+license=('{pkg.license}')
+arch=('any')
+source=('{pkg.download_url}')
+md5sums=('{pkg.md5}')
+
+build() {{
+ cd $srcdir/{pkg.name}-{pkg.version}
+ {pkg.pyversion} setup.py build
+}}
+
+package() {{
+ cd $srcdir/{pkg.name}-{pkg.version}
+ {pkg.pyversion} setup.py install --root="$pkgdir" --optimize=1 {pkg.setup_args}
+}}
+"""
+
+SOURCEFILE_TYPE_RE = re.compile(".*\.(tar|zip|gz|z|bz2?|xz)", re.IGNORECASE)
+
+class pip2archException(Exception): pass
+class VersionNotFound(pip2archException): pass
+class LackOfInformation(pip2archException): pass
+
+class Package(object):
+ logging.info('Creating Server Proxy object')
+ client = ServerProxy('http://pypi.python.org/pypi')
+ depends = []
+ makedepends = []
+ data_received = False
+ setup_args = ''
+
+ def get_package(self, name, outname, pyversion ,version=None):
+ if version is None:
+ versions = self.client.package_releases(name)
+ if len(versions) > 1:
+ version = self.choose_version(versions)
+ else:
+ logging.info('Using version %s' % versions[0])
+ version = versions[0]
+ self.version = version
+ self.pyversion = pyversion
+
+ data = self.client.release_data(name, version)
+ logging.info('Got release_data from PyPi')
+
+ raw_urls = self.client.release_urls(name, version)
+ logging.info('Got release_urls from PyPi')
+ if not len(data):
+ raise VersionNotFound('PyPi did not return any information for version {0}'.format(self.version))
+ elif not len(raw_urls):
+ if 'download_url' in data:
+ download_url = data['download_url']
+ if SOURCEFILE_TYPE_RE.match(data['download_url']) is None:
+ raise LackOfInformation("Couldn't find any suitable source")
+ else:
+ urls = {'url': download_url}
+ logging.warning('Got download link but no md5, you may have to search it by youself or generate it')
+ else:
+ raise LackOfInformation('PyPi did not return the necessary information to create the PKGBUILD')
+ else:
+ urls = {}
+ for url in raw_urls:
+ if SOURCEFILE_TYPE_RE.match(url['filename']):
+ urls = url
+ if not urls:
+ raise pip2archException
+ ('Selected package version had no suitable sources')
+ logging.info('Parsed release_urls data')
+
+
+ self.distributepackage = 'python2-distribute' if\
+ self.pyversion != 'python' else 'python3'
+ logging.info("Set distribute package as {0}".format(self.distributepackage))
+
+ if outname is not None:
+ self.outname = outname.lower()
+ elif any(re.search(r'Librar(ies|y)', item) for item in data['classifiers']):
+ #if this is a library
+ self.outname = '{pyversion}-{pkgname}'.format(
+ pyversion=self.pyversion, pkgname=name).lower()
+ logging.info('Automaticly added {0} to the front of the package'.format(self.pyversion))
+ else:
+ self.outname = name.lower()
+
+ #check for licenes texts
+ if len(data.get('license', '')) > 10:
+ self.license = 'CUSTOM'
+ else:
+ self.license = data.get('license', 'UNKNOWN')
+
+ try:
+ self.name = data['name']
+ self.description = data['summary']
+ self.download_url = urls.get('url', '')
+ self.md5 = urls.get('md5_digest', '')
+ self.url = data.get('home_page', '')
+ self.depends = data.get('requires', [])
+ except KeyError:
+ raise pip2archException('PyPi did not return needed information')
+ logging.info('Parsed other data')
+ self.data_received = True
+
+ def search(self, term, interactive=False):
+ results = self.client.search({'description': term[1:],
+ 'name': term[1:]},
+ 'or')
+ logging.info('Got search results for term {term} from PyPi server'.format(term=term))
+
+ #If no results
+ if not results:
+ print ('No packages found')
+ return
+ for i, result in enumerate(results):
+ i += 1
+ print ('{index}. {name} - {summary}'.format(index=i, name=result['name'], summary=result['summary']))
+
+ #If we don't want talking, exit here
+ if not interactive:
+ #self.data_received = False
+ return
+
+ selection = raw_input('Enter the number of the PyPi package you would like to process\n')
+
+ try:
+ selection = int(selection.strip())
+ selection -= 1
+ chosen = results[selection]
+ except (TypeError, IndexError):
+ print ('Not a valid selection. Must be integer in range 1 - {length}'.format(length=len(results)))
+ retry = raw_input('Retry? [Y/n]\n')
+ if retry.strip()[0].lower() != 'n':
+ #offer recurse on failure, maybe user will be smarter this time -.-
+ return self.search(term)
+ else:
+ return
+
+ name = chosen['name']
+ outname = chosen['name']
+
+ return self.get_package(name, outname)
+
+ def choose_version(self, versions):
+ print ('Multiple versions found:')
+ print (', '.join(versions))
+ ver = raw_input('Which version would you like to use? ')
+ if ver in versions:
+ return ver
+ else:
+ print ('That was NOT one of the choices...')
+ print ('Try again')
+ return self.choose_version(versions)
+
+ def add_depends(self, depends):
+ self.depends += depends
+
+ def add_makedepends(self, makedepends):
+ self.makedepends += makedepends
+
+ def render(self):
+ depends = "'" + "' '".join(d for d in self.depends) + "'" if self.depends else ''
+ makedepends = "'" + "' '".join(d for d in self.makedepends) + "'" if self.makedepends else ''
+ return BLANK_PKGBUILD.format(pkg=self,
+ date=datetime.date.today(),
+ depends=depends,
+ makedepends=makedepends)
+
+def set_logging_level(level_str):
+ level = getattr(logging, level_str.upper())
+ logging.root.setLevel(level)
+
+def main():
+ parser = argparse.ArgumentParser(description='Convert a PyPi package into an Arch Linux PKGBUILD.')
+ parser.add_argument('pkgname', metavar='PKGNAME', action='store',
+ help='Name of PyPi package for pip2arch to process')
+ parser.add_argument('-v', '--version', dest='version', action='store',
+ help='The version of the speciied PyPi package to process')
+ parser.add_argument('-p', '--python-version', dest='pyversion', action='store',
+ default='python', choices=('python','python2'),
+ help='The python version to build and install the package with')
+ parser.add_argument('-o', '--output', dest='outfile', action='store',
+ default='PKGBUILD',
+ help='The file to output the generated PKGBUILD to')
+ parser.add_argument('-s', '--search', dest='search', action='store_true',
+ help="Search for given package name, instead of building PKGBUILD")
+ parser.add_argument('-i', '--interactive', dest='interactive', action='store_true',
+ help="Makes all commands interactive, prompting user for input.")
+ parser.add_argument('-d', '--dependencies', dest='depends', action='append',
+ help="The name of a package that should be added to the depends array")
+ parser.add_argument('-m', '--make-dependencies', dest='makedepends', action='append',
+ help="The name of a package that should be added to the makedepends array")
+ parser.add_argument('-n', '--output-package-name', dest='outname', action='store', default=None,
+ help='The name of the package that pip2arch will generate')
+ parser.add_argument('--logging-level', dest='logging_level', action='store', default='warning', choices=('warning', 'info', 'debug'),
+ help='The level of logging messages to show')
+ parser.add_argument('-b', '--build-args', dest='build_args', action='store',
+ help='Custom arguments for the python install setup.py file')
+
+ args = parser.parse_args()
+
+ set_logging_level(args.logging_level)
+
+ p = Package()
+
+ if args.search:
+ p.search(args.pkgname, interactive=args.interactive)
+ else:
+ p.get_package(name=args.pkgname, pyversion= args.pyversion,
+ version=args.version, outname=args.outname)
+
+ if args.depends:
+ p.add_depends(args.depends)
+ if args.makedepends:
+ p.add_makedepends(args.makedepends)
+ if args.build_args:
+ p.setup_args = args.build_args
+ if p.data_received:
+ print ('Got package information')
+ with open(args.outfile, 'w') as f:
+ f.write(p.render())
+ print ('PKGBUILD written')
+
+if __name__ == '__main__':
+ try:
+ main()
+ except pip2archException as e:
+ sys.exit('Pip2Arch error: {0}'.format(e))
+ else:
+ sys.exit(0)