Florian Festi 82f61b
diff --git a/scripts/pythondistdeps.py b/scripts/pythondistdeps.py
Florian Festi 82f61b
new file mode 100755
Florian Festi 82f61b
index 0000000..8a2f43d
Florian Festi 82f61b
--- /dev/null
Florian Festi 82f61b
+++ b/scripts/pythondistdeps.py
Florian Festi 82f61b
@@ -0,0 +1,223 @@
Florian Festi 82f61b
+#!/usr/bin/python
Florian Festi 82f61b
+# -*- coding: utf-8 -*-
Florian Festi 82f61b
+#
Florian Festi 82f61b
+# Copyright 2010 Per Øyvind Karlsen <proyvind@moondrake.org>
Florian Festi 82f61b
+# Copyright 2015 Neal Gompa <ngompa13@gmail.com>
Florian Festi 82f61b
+#
Florian Festi 82f61b
+# This program is free software. It may be redistributed and/or modified under
Florian Festi 82f61b
+# the terms of the LGPL version 2.1 (or later).
Florian Festi 82f61b
+#
Florian Festi 82f61b
+# RPM python dependency generator, using .egg-info/.egg-link/.dist-info data
Florian Festi 82f61b
+#
Florian Festi 82f61b
+
Florian Festi 82f61b
+from __future__ import print_function
Florian Festi 82f61b
+from getopt import getopt
Florian Festi 82f61b
+from os.path import basename, dirname, isdir, sep
Florian Festi 82f61b
+from sys import argv, stdin, version
Florian Festi 82f61b
+from distutils.sysconfig import get_python_lib
Florian Festi 82f61b
+
Florian Festi 82f61b
+
Florian Festi 82f61b
+opts, args = getopt(
Florian Festi 82f61b
+    argv[1:], 'hPRrCEMLl:',
Florian Festi 82f61b
+    ['help', 'provides', 'requires', 'recommends', 'conflicts', 'extras', 'majorver-provides', 'legacy-provides' , 'legacy'])
Florian Festi 82f61b
+
Florian Festi 82f61b
+Provides = False
Florian Festi 82f61b
+Requires = False
Florian Festi 82f61b
+Recommends = False
Florian Festi 82f61b
+Conflicts = False
Florian Festi 82f61b
+Extras = False
Florian Festi 82f61b
+Provides_PyMajorVer_Variant = False
Florian Festi 82f61b
+legacy_Provides = False
Florian Festi 82f61b
+legacy = False
Florian Festi 82f61b
+
Florian Festi 82f61b
+for o, a in opts:
Florian Festi 82f61b
+    if o in ('-h', '--help'):
Florian Festi 82f61b
+        print('-h, --help\tPrint help')
Florian Festi 82f61b
+        print('-P, --provides\tPrint Provides')
Florian Festi 82f61b
+        print('-R, --requires\tPrint Requires')
Florian Festi 82f61b
+        print('-r, --recommends\tPrint Recommends')
Florian Festi 82f61b
+        print('-C, --conflicts\tPrint Conflicts')
Florian Festi 82f61b
+        print('-E, --extras\tPrint Extras ')
Florian Festi 82f61b
+        print('-M, --majorver-provides\tPrint extra Provides with Python major version only')
Florian Festi 82f61b
+        print('-L, --legacy-provides\tPrint extra legacy pythonegg Provides')
Florian Festi 82f61b
+        print('-l, --legacy\tPrint legacy pythonegg Provides/Requires instead')
Florian Festi 82f61b
+        exit(1)
Florian Festi 82f61b
+    elif o in ('-P', '--provides'):
Florian Festi 82f61b
+        Provides = True
Florian Festi 82f61b
+    elif o in ('-R', '--requires'):
Florian Festi 82f61b
+        Requires = True
Florian Festi 82f61b
+    elif o in ('-r', '--recommends'):
Florian Festi 82f61b
+        Recommends = True
Florian Festi 82f61b
+    elif o in ('-C', '--conflicts'):
Florian Festi 82f61b
+        Conflicts = True
Florian Festi 82f61b
+    elif o in ('-E', '--extras'):
Florian Festi 82f61b
+        Extras = True
Florian Festi 82f61b
+    elif o in ('-M', '--majorver-provides'):
Florian Festi 82f61b
+        Provides_PyMajorVer_Variant = True
Florian Festi 82f61b
+    elif o in ('-L', '--legacy-provides'):
Florian Festi 82f61b
+        legacy_Provides = True
Florian Festi 82f61b
+    elif o in ('-l', '--legacy'):
Florian Festi 82f61b
+        legacy = True
Florian Festi 82f61b
+
Florian Festi 82f61b
+if Requires:
Florian Festi 82f61b
+    py_abi = True
Florian Festi 82f61b
+else:
Florian Festi 82f61b
+    py_abi = False
Florian Festi 82f61b
+py_deps = {}
Florian Festi 82f61b
+if args:
Florian Festi 82f61b
+    files = args
Florian Festi 82f61b
+else:
Florian Festi 82f61b
+    files = stdin.readlines()
Florian Festi 82f61b
+
Florian Festi 82f61b
+for f in files:
Florian Festi 82f61b
+    f = f.strip()
Florian Festi 82f61b
+    lower = f.lower()
Florian Festi 82f61b
+    name = 'python(abi)'
Florian Festi 82f61b
+    # add dependency based on path, versioned if within versioned python directory
Florian Festi 82f61b
+    if py_abi and (lower.endswith('.py') or lower.endswith('.pyc') or lower.endswith('.pyo')):
Florian Festi 82f61b
+        if name not in py_deps:
Florian Festi 82f61b
+            py_deps[name] = []
Florian Festi 82f61b
+        purelib = get_python_lib(standard_lib=1, plat_specific=0).split(version[:3])[0]
Florian Festi 82f61b
+        platlib = get_python_lib(standard_lib=1, plat_specific=1).split(version[:3])[0]
Florian Festi 82f61b
+        for lib in (purelib, platlib):
Florian Festi 82f61b
+            if lib in f:
Florian Festi 82f61b
+                spec = ('==', f.split(lib)[1].split(sep)[0])
Florian Festi 82f61b
+                if spec not in py_deps[name]:
Florian Festi 82f61b
+                    py_deps[name].append(spec)
Florian Festi 82f61b
+
Florian Festi 82f61b
+    # XXX: hack to workaround RPM internal dependency generator not passing directories
Florian Festi 82f61b
+    lower_dir = dirname(lower)
Florian Festi 82f61b
+    if lower_dir.endswith('.egg') or \
Florian Festi 82f61b
+            lower_dir.endswith('.egg-info') or \
Florian Festi 82f61b
+            lower_dir.endswith('.egg-link') or \
Florian Festi 82f61b
+            lower_dir.endswith('.dist-info'):
Florian Festi 82f61b
+        lower = lower_dir
Florian Festi 82f61b
+        f = dirname(f)
Florian Festi 82f61b
+    # Determine provide, requires, conflicts & recommends based on egg/dist metadata
Florian Festi 82f61b
+    if lower.endswith('.egg') or \
Florian Festi 82f61b
+            lower.endswith('.egg-info') or \
Florian Festi 82f61b
+            lower.endswith('.egg-link') or \
Florian Festi 82f61b
+            lower.endswith('.dist-info'):
Florian Festi 82f61b
+        # This import is very slow, so only do it if needed
Florian Festi 82f61b
+        from pkg_resources import Distribution, FileMetadata, PathMetadata
Florian Festi 82f61b
+        dist_name = basename(f)
Florian Festi 82f61b
+        if isdir(f):
Florian Festi 82f61b
+            path_item = dirname(f)
Florian Festi 82f61b
+            metadata = PathMetadata(path_item, f)
Florian Festi 82f61b
+        else:
Florian Festi 82f61b
+            path_item = f
Florian Festi 82f61b
+            metadata = FileMetadata(f)
Florian Festi 82f61b
+        dist = Distribution.from_location(path_item, dist_name, metadata)
Florian Festi 82f61b
+        if (Provides_PyMajorVer_Variant or legacy_Provides or legacy) and Provides:
Florian Festi 82f61b
+            # Get the Python major version
Florian Festi 82f61b
+            pyver_major = dist.py_version.split('.')[0]
Florian Festi 82f61b
+        if Provides:
Florian Festi 82f61b
+            # If egg/dist metadata says package name is python, we provide python(abi)
Florian Festi 82f61b
+            if dist.key == 'python':
Florian Festi 82f61b
+                name = 'python(abi)'
Florian Festi 82f61b
+                if name not in py_deps:
Florian Festi 82f61b
+                    py_deps[name] = []
Florian Festi 82f61b
+                py_deps[name].append(('==', dist.py_version))
Florian Festi 82f61b
+            if not legacy:
Florian Festi 82f61b
+                name = 'python{}dist({})'.format(dist.py_version, dist.key)
Florian Festi 82f61b
+                if name not in py_deps:
Florian Festi 82f61b
+                    py_deps[name] = []
Florian Festi 82f61b
+            if Provides_PyMajorVer_Variant:
Florian Festi 82f61b
+                pymajor_name = 'python{}dist({})'.format(pyver_major, dist.key)
Florian Festi 82f61b
+                if pymajor_name not in py_deps:
Florian Festi 82f61b
+                    py_deps[pymajor_name] = []
Florian Festi 82f61b
+            if legacy or legacy_Provides:
Florian Festi 82f61b
+                legacy_name = 'pythonegg({})({})'.format(pyver_major, dist.key)
Florian Festi 82f61b
+                if legacy_name not in py_deps:
Florian Festi 82f61b
+                    py_deps[legacy_name] = []
Florian Festi 82f61b
+            if dist.version:
Florian Festi 82f61b
+                spec = ('==', dist.version)
Florian Festi 82f61b
+                if spec not in py_deps[name]:
Florian Festi 82f61b
+                    if not legacy:
Florian Festi 82f61b
+                        py_deps[name].append(spec)
Florian Festi 82f61b
+                    if Provides_PyMajorVer_Variant:
Florian Festi 82f61b
+                        py_deps[pymajor_name].append(spec)
Florian Festi 82f61b
+                    if legacy or legacy_Provides:
Florian Festi 82f61b
+                        py_deps[legacy_name].append(spec)
Florian Festi 82f61b
+        if Requires or (Recommends and dist.extras):
Florian Festi 82f61b
+            name = 'python(abi)'
Florian Festi 82f61b
+            # If egg/dist metadata says package name is python, we don't add dependency on python(abi)
Florian Festi 82f61b
+            if dist.key == 'python':
Florian Festi 82f61b
+                py_abi = False
Florian Festi 82f61b
+                if name in py_deps:
Florian Festi 82f61b
+                    py_deps.pop(name)
Florian Festi 82f61b
+            elif py_abi and dist.py_version:
Florian Festi 82f61b
+                if name not in py_deps:
Florian Festi 82f61b
+                    py_deps[name] = []
Florian Festi 82f61b
+                spec = ('==', dist.py_version)
Florian Festi 82f61b
+                if spec not in py_deps[name]:
Florian Festi 82f61b
+                    py_deps[name].append(spec)
Florian Festi 82f61b
+            deps = dist.requires()
Florian Festi 82f61b
+            if Recommends:
Florian Festi 82f61b
+                depsextras = dist.requires(extras=dist.extras)
Florian Festi 82f61b
+                if not Requires:
Florian Festi 82f61b
+                    for dep in reversed(depsextras):
Florian Festi 82f61b
+                        if dep in deps:
Florian Festi 82f61b
+                            depsextras.remove(dep)
Florian Festi 82f61b
+                deps = depsextras
Florian Festi 82f61b
+            # add requires/recommends based on egg/dist metadata
Florian Festi 82f61b
+            for dep in deps:
Florian Festi 82f61b
+                if legacy:
Florian Festi 82f61b
+                    name = 'pythonegg({})({})'.format(pyver_major, dep.key)
Florian Festi 82f61b
+                else:
Florian Festi 82f61b
+                    name = 'python{}dist({})'.format(dist.py_version, dep.key)
Florian Festi 82f61b
+                for spec in dep.specs:
Florian Festi 82f61b
+                    if spec[0] != '!=':
Florian Festi 82f61b
+                        if name not in py_deps:
Florian Festi 82f61b
+                            py_deps[name] = []
Florian Festi 82f61b
+                        if spec not in py_deps[name]:
Florian Festi 82f61b
+                            py_deps[name].append(spec)
Florian Festi 82f61b
+                if not dep.specs:
Florian Festi 82f61b
+                    py_deps[name] = []
Florian Festi 82f61b
+        # Unused, for automatic sub-package generation based on 'extras' from egg/dist metadata
Florian Festi 82f61b
+        # TODO: implement in rpm later, or...?
Florian Festi 82f61b
+        if Extras:
Florian Festi 82f61b
+            deps = dist.requires()
Florian Festi 82f61b
+            extras = dist.extras
Florian Festi 82f61b
+            print(extras)
Florian Festi 82f61b
+            for extra in extras:
Florian Festi 82f61b
+                print('%%package\textras-{}'.format(extra))
Florian Festi 82f61b
+                print('Summary:\t{} extra for {} python package'.format(extra, dist.key))
Florian Festi 82f61b
+                print('Group:\t\tDevelopment/Python')
Florian Festi 82f61b
+                depsextras = dist.requires(extras=[extra])
Florian Festi 82f61b
+                for dep in reversed(depsextras):
Florian Festi 82f61b
+                    if dep in deps:
Florian Festi 82f61b
+                        depsextras.remove(dep)
Florian Festi 82f61b
+                deps = depsextras
Florian Festi 82f61b
+                for dep in deps:
Florian Festi 82f61b
+                    for spec in dep.specs:
Florian Festi 82f61b
+                        if spec[0] == '!=':
Florian Festi 82f61b
+                            print('Conflicts:\t{} {} {}'.format(dep.key, '==', spec[1]))
Florian Festi 82f61b
+                        else:
Florian Festi 82f61b
+                            print('Requires:\t{} {} {}'.format(dep.key, spec[0], spec[1]))
Florian Festi 82f61b
+                print('%%description\t{}'.format(extra))
Florian Festi 82f61b
+                print('{} extra for {} python package'.format(extra, dist.key))
Florian Festi 82f61b
+                print('%%files\t\textras-{}\n'.format(extra))
Florian Festi 82f61b
+        if Conflicts:
Florian Festi 82f61b
+            # Should we really add conflicts for extras?
Florian Festi 82f61b
+            # Creating a meta package per extra with recommends on, which has
Florian Festi 82f61b
+            # the requires/conflicts in stead might be a better solution...
Florian Festi 82f61b
+            for dep in dist.requires(extras=dist.extras):
Florian Festi 82f61b
+                name = dep.key
Florian Festi 82f61b
+                for spec in dep.specs:
Florian Festi 82f61b
+                    if spec[0] == '!=':
Florian Festi 82f61b
+                        if name not in py_deps:
Florian Festi 82f61b
+                            py_deps[name] = []
Florian Festi 82f61b
+                        spec = ('==', spec[1])
Florian Festi 82f61b
+                        if spec not in py_deps[name]:
Florian Festi 82f61b
+                            py_deps[name].append(spec)
Florian Festi 82f61b
+names = list(py_deps.keys())
Florian Festi 82f61b
+names.sort()
Florian Festi 82f61b
+for name in names:
Florian Festi 82f61b
+    if py_deps[name]:
Florian Festi 82f61b
+        # Print out versioned provides, requires, recommends, conflicts
Florian Festi 82f61b
+        for spec in py_deps[name]:
Florian Festi 82f61b
+            print('{} {} {}'.format(name, spec[0], spec[1]))
Florian Festi 82f61b
+    else:
Florian Festi 82f61b
+        # Print out unversioned provides, requires, recommends, conflicts
Florian Festi 82f61b
+        print(name)