upgrade rpm version to 4.14.1
[platform/upstream/rpm.git] / scripts / pythondistdeps.py
1
2 # -*- coding: utf-8 -*-
3 #
4 # Copyright 2010 Per Ã˜yvind Karlsen <proyvind@moondrake.org>
5 # Copyright 2015 Neal Gompa <ngompa13@gmail.com>
6 #
7 # This program is free software. It may be redistributed and/or modified under
8 # the terms of the LGPL version 2.1 (or later).
9 #
10 # RPM python dependency generator, using .egg-info/.egg-link/.dist-info data
11 #
12
13 from __future__ import print_function
14 from getopt import getopt
15 from os.path import basename, dirname, isdir, sep
16 from sys import argv, stdin, version
17 from distutils.sysconfig import get_python_lib
18 from warnings import warn
19
20
21 opts, args = getopt(
22     argv[1:], 'hPRrCEMmLl:',
23     ['help', 'provides', 'requires', 'recommends', 'conflicts', 'extras', 'majorver-provides', 'majorver-only', 'legacy-provides' , 'legacy'])
24
25 Provides = False
26 Requires = False
27 Recommends = False
28 Conflicts = False
29 Extras = False
30 Provides_PyMajorVer_Variant = False
31 PyMajorVer_Deps = False
32 legacy_Provides = False
33 legacy = False
34
35 for o, a in opts:
36     if o in ('-h', '--help'):
37         print('-h, --help\tPrint help')
38         print('-P, --provides\tPrint Provides')
39         print('-R, --requires\tPrint Requires')
40         print('-r, --recommends\tPrint Recommends')
41         print('-C, --conflicts\tPrint Conflicts')
42         print('-E, --extras\tPrint Extras ')
43         print('-M, --majorver-provides\tPrint extra Provides with Python major version only')
44         print('-m, --majorver-only\tPrint Provides/Requires with Python major version only')
45         print('-L, --legacy-provides\tPrint extra legacy pythonegg Provides')
46         print('-l, --legacy\tPrint legacy pythonegg Provides/Requires instead')
47         exit(1)
48     elif o in ('-P', '--provides'):
49         Provides = True
50     elif o in ('-R', '--requires'):
51         Requires = True
52     elif o in ('-r', '--recommends'):
53         Recommends = True
54     elif o in ('-C', '--conflicts'):
55         Conflicts = True
56     elif o in ('-E', '--extras'):
57         Extras = True
58     elif o in ('-M', '--majorver-provides'):
59         Provides_PyMajorVer_Variant = True
60     elif o in ('-m', '--majorver-only'):
61         PyMajorVer_Deps = True
62     elif o in ('-L', '--legacy-provides'):
63         legacy_Provides = True
64     elif o in ('-l', '--legacy'):
65         legacy = True
66
67 if Requires:
68     py_abi = True
69 else:
70     py_abi = False
71 py_deps = {}
72 if args:
73     files = args
74 else:
75     files = stdin.readlines()
76
77 for f in files:
78     f = f.strip()
79     lower = f.lower()
80     name = 'python(abi)'
81     # add dependency based on path, versioned if within versioned python directory
82     if py_abi and (lower.endswith('.py') or lower.endswith('.pyc') or lower.endswith('.pyo')):
83         if name not in py_deps:
84             py_deps[name] = []
85         purelib = get_python_lib(standard_lib=1, plat_specific=0).split(version[:3])[0]
86         platlib = get_python_lib(standard_lib=1, plat_specific=1).split(version[:3])[0]
87         for lib in (purelib, platlib):
88             if lib in f:
89                 spec = ('==', f.split(lib)[1].split(sep)[0])
90                 if spec not in py_deps[name]:
91                     py_deps[name].append(spec)
92
93     # XXX: hack to workaround RPM internal dependency generator not passing directories
94     lower_dir = dirname(lower)
95     if lower_dir.endswith('.egg') or \
96             lower_dir.endswith('.egg-info') or \
97             lower_dir.endswith('.dist-info'):
98         lower = lower_dir
99         f = dirname(f)
100     # Determine provide, requires, conflicts & recommends based on egg/dist metadata
101     if lower.endswith('.egg') or \
102             lower.endswith('.egg-info') or \
103             lower.endswith('.dist-info'):
104         # This import is very slow, so only do it if needed
105         from pkg_resources import Distribution, FileMetadata, PathMetadata
106         dist_name = basename(f)
107         if isdir(f):
108             path_item = dirname(f)
109             metadata = PathMetadata(path_item, f)
110         else:
111             path_item = f
112             metadata = FileMetadata(f)
113         dist = Distribution.from_location(path_item, dist_name, metadata)
114         # Check if py_version is defined in the metadata file/directory name
115         if not dist.py_version:
116             # Try to parse the Python version from the path the metadata
117             # resides at (e.g. /usr/lib/pythonX.Y/site-packages/...)
118             import re
119             res = re.search(r"/python(?P<pyver>\d+\.\d)/", path_item)
120             if res:
121                 dist.py_version = res.group('pyver')
122             else:
123                 warn("Version for {!r} has not been found".format(dist), RuntimeWarning)
124                 continue
125         if Provides_PyMajorVer_Variant or PyMajorVer_Deps or legacy_Provides or legacy:
126             # Get the Python major version
127             pyver_major = dist.py_version.split('.')[0]
128         if Provides:
129             # If egg/dist metadata says package name is python, we provide python(abi)
130             if dist.key == 'python':
131                 name = 'python(abi)'
132                 if name not in py_deps:
133                     py_deps[name] = []
134                 py_deps[name].append(('==', dist.py_version))
135             if not legacy or not PyMajorVer_Deps:
136                 name = 'python{}dist({})'.format(dist.py_version, dist.key)
137                 if name not in py_deps:
138                     py_deps[name] = []
139             if Provides_PyMajorVer_Variant or PyMajorVer_Deps:
140                 pymajor_name = 'python{}dist({})'.format(pyver_major, dist.key)
141                 if pymajor_name not in py_deps:
142                     py_deps[pymajor_name] = []
143             if legacy or legacy_Provides:
144                 legacy_name = 'pythonegg({})({})'.format(pyver_major, dist.key)
145                 if legacy_name not in py_deps:
146                     py_deps[legacy_name] = []
147             if dist.version:
148                 spec = ('==', dist.version)
149                 if spec not in py_deps[name]:
150                     if not legacy:
151                         py_deps[name].append(spec)
152                     if Provides_PyMajorVer_Variant:
153                         py_deps[pymajor_name].append(spec)
154                     if legacy or legacy_Provides:
155                         py_deps[legacy_name].append(spec)
156         if Requires or (Recommends and dist.extras):
157             name = 'python(abi)'
158             # If egg/dist metadata says package name is python, we don't add dependency on python(abi)
159             if dist.key == 'python':
160                 py_abi = False
161                 if name in py_deps:
162                     py_deps.pop(name)
163             elif py_abi and dist.py_version:
164                 if name not in py_deps:
165                     py_deps[name] = []
166                 spec = ('==', dist.py_version)
167                 if spec not in py_deps[name]:
168                     py_deps[name].append(spec)
169             deps = dist.requires()
170             if Recommends:
171                 depsextras = dist.requires(extras=dist.extras)
172                 if not Requires:
173                     for dep in reversed(depsextras):
174                         if dep in deps:
175                             depsextras.remove(dep)
176                 deps = depsextras
177             # add requires/recommends based on egg/dist metadata
178             for dep in deps:
179                 if legacy:
180                     name = 'pythonegg({})({})'.format(pyver_major, dep.key)
181                 else:
182                     if PyMajorVer_Deps:
183                         name = 'python{}dist({})'.format(pyver_major, dep.key)
184                     else:
185                         name = 'python{}dist({})'.format(dist.py_version, dep.key)
186                 for spec in dep.specs:
187                     if spec[0] != '!=':
188                         if name not in py_deps:
189                             py_deps[name] = []
190                         if spec not in py_deps[name]:
191                             py_deps[name].append(spec)
192                 if not dep.specs:
193                     py_deps[name] = []
194         # Unused, for automatic sub-package generation based on 'extras' from egg/dist metadata
195         # TODO: implement in rpm later, or...?
196         if Extras:
197             deps = dist.requires()
198             extras = dist.extras
199             print(extras)
200             for extra in extras:
201                 print('%%package\textras-{}'.format(extra))
202                 print('Summary:\t{} extra for {} python package'.format(extra, dist.key))
203                 print('Group:\t\tDevelopment/Python')
204                 depsextras = dist.requires(extras=[extra])
205                 for dep in reversed(depsextras):
206                     if dep in deps:
207                         depsextras.remove(dep)
208                 deps = depsextras
209                 for dep in deps:
210                     for spec in dep.specs:
211                         if spec[0] == '!=':
212                             print('Conflicts:\t{} {} {}'.format(dep.key, '==', spec[1]))
213                         else:
214                             print('Requires:\t{} {} {}'.format(dep.key, spec[0], spec[1]))
215                 print('%%description\t{}'.format(extra))
216                 print('{} extra for {} python package'.format(extra, dist.key))
217                 print('%%files\t\textras-{}\n'.format(extra))
218         if Conflicts:
219             # Should we really add conflicts for extras?
220             # Creating a meta package per extra with recommends on, which has
221             # the requires/conflicts in stead might be a better solution...
222             for dep in dist.requires(extras=dist.extras):
223                 name = dep.key
224                 for spec in dep.specs:
225                     if spec[0] == '!=':
226                         if name not in py_deps:
227                             py_deps[name] = []
228                         spec = ('==', spec[1])
229                         if spec not in py_deps[name]:
230                             py_deps[name].append(spec)
231 names = list(py_deps.keys())
232 names.sort()
233 for name in names:
234     if py_deps[name]:
235         # Print out versioned provides, requires, recommends, conflicts
236         for spec in py_deps[name]:
237             print('{} {} {}'.format(name, spec[0], spec[1]))
238     else:
239         # Print out unversioned provides, requires, recommends, conflicts
240         print(name)