~ubuntu-core-dev/ubuntu-release-upgrader/trunk

« back to all changes in this revision

Viewing changes to tests/patchdir/pycompile_orig

  • Committer: Balint Reczey
  • Date: 2019-12-17 20:29:55 UTC
  • Revision ID: balint.reczey@canonical.com-20191217202955-nqe4xz2c54s60y59
Moved to git at https://git.launchpad.net/ubuntu-release-upgrader

Show diffs side-by-side

added added

removed removed

Lines of Context:
1
 
#! /usr/bin/python
2
 
# -*- coding: UTF-8 -*-
3
 
# vim: et ts=4 sw=4
4
 
 
5
 
# Copyright © 2010 Piotr Ożarowski <piotr@debian.org>
6
 
#
7
 
# Permission is hereby granted, free of charge, to any person obtaining a copy
8
 
# of this software and associated documentation files (the "Software"), to deal
9
 
# in the Software without restriction, including without limitation the rights
10
 
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 
# copies of the Software, and to permit persons to whom the Software is
12
 
# furnished to do so, subject to the following conditions:
13
 
#
14
 
# The above copyright notice and this permission notice shall be included in
15
 
# all copies or substantial portions of the Software.
16
 
#
17
 
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23
 
# THE SOFTWARE.
24
 
 
25
 
from __future__ import with_statement
26
 
import logging
27
 
import optparse
28
 
import sys
29
 
from os import environ, listdir, walk
30
 
from os.path import abspath, exists, isdir, isfile, join
31
 
from subprocess import PIPE, Popen
32
 
sys.path.insert(1, '/usr/share/python/')
33
 
from debpython.version import SUPPORTED, debsorted, vrepr, \
34
 
        get_requested_versions, parse_vrange, getver
35
 
from debpython.option import Option, compile_regexpr
36
 
from debpython.pydist import PUBLIC_DIR_RE
37
 
from debpython.tools import memoize
38
 
 
39
 
# initialize script
40
 
logging.basicConfig(format='%(levelname).1s: %(module)s:%(lineno)d: '
41
 
                           '%(message)s')
42
 
log = logging.getLogger(__name__)
43
 
STDINS = {}
44
 
WORKERS = {}
45
 
 
46
 
"""TODO: move it to manpage
47
 
Examples:
48
 
    pycompile -p python-mako # package's public files
49
 
    pycompile -p foo /usr/share/foo # package's private files
50
 
    pycompile -p foo -V 2.6- /usr/share/foo # private files, Python >= 2.6
51
 
    pycompile -V 2.6 /usr/lib/python2.6/dist-packages # python2.6 only
52
 
    pycompile -V 2.6 /usr/lib/foo/bar.py # python2.6 only
53
 
"""
54
 
 
55
 
 
56
 
### FILES ######################################################
57
 
def get_directory_files(dname):
58
 
    """Generate *.py file names available in given directory."""
59
 
    if isfile(dname) and dname.endswith('.py'):
60
 
        yield dname
61
 
    else:
62
 
        for root, dirs, file_names in walk(abspath(dname)):
63
 
            #if root != dname and not exists(join(root, '__init__.py')):
64
 
            #    del dirs[:]
65
 
            #    continue
66
 
            for fn in file_names:
67
 
                if fn.endswith('.py'):
68
 
                    yield join(root, fn)
69
 
 
70
 
 
71
 
def get_package_files(package_name):
72
 
    """Generate *.py file names available in given package."""
73
 
    process = Popen("/usr/bin/dpkg -L %s" % package_name,\
74
 
                    shell=True, stdout=PIPE)
75
 
    stdout, stderr = process.communicate()
76
 
    if process.returncode != 0:
77
 
        log.error('cannot get content of %s', package_name)
78
 
        exit(2)
79
 
    for line in stdout.split('\n'):
80
 
        if line.endswith('.py'):
81
 
            yield line
82
 
 
83
 
 
84
 
def get_private_files(files, dname):
85
 
    """Generate *.py file names that match given directory."""
86
 
    for fn in files:
87
 
        if fn.startswith(dname):
88
 
            yield fn
89
 
 
90
 
 
91
 
def get_public_files(files, versions):
92
 
    """Generate *.py file names that match given versions."""
93
 
    versions_str = set("%d.%d" % i for i in versions)
94
 
    for fn in files:
95
 
        if fn.startswith('/usr/lib/python') and \
96
 
           fn[15:18] in versions_str:
97
 
            yield fn
98
 
 
99
 
 
100
 
### EXCLUDES ###################################################
101
 
@memoize
102
 
def get_exclude_patterns_from_dir(name='/usr/share/python/bcep/'):
103
 
    """Return patterns for files that shouldn't be bytecompiled."""
104
 
    if not isdir(name):
105
 
        return []
106
 
 
107
 
    result = []
108
 
    for fn in listdir(name):
109
 
        with file(join(name, fn), 'r') as lines:
110
 
            for line in lines:
111
 
                type_, vrange, dname, pattern = line.split('|', 3)
112
 
                vrange = parse_vrange(vrange)
113
 
                versions = get_requested_versions(vrange, available=True)
114
 
                if not versions:
115
 
                    # pattern doesn't match installed Python versions
116
 
                    continue
117
 
                pattern = pattern.rstrip('\n')
118
 
                if type_ == 're':
119
 
                    pattern = compile_regexpr(None, None, pattern)
120
 
                result.append((type_, versions, dname, pattern))
121
 
    return result
122
 
 
123
 
 
124
 
def get_exclude_patterns(directory='/', patterns=None, versions=None):
125
 
    """Return patterns for files that shouldn't be compiled in given dir."""
126
 
    if patterns:
127
 
        if versions is None:
128
 
            versions = set(SUPPORTED)
129
 
        patterns = [('re', versions, directory, i) for i in patterns]
130
 
    else:
131
 
        patterns = []
132
 
 
133
 
    for type_, vers, dname, pattern in get_exclude_patterns_from_dir():
134
 
        # skip patterns that do not match requested directory
135
 
        if not dname.startswith(directory[:len(dname)]):
136
 
            continue
137
 
        # skip patterns that do not match requested versions
138
 
        if versions and not versions & vers:
139
 
            continue
140
 
        patterns.append((type_, vers, dname, pattern))
141
 
    return patterns
142
 
 
143
 
 
144
 
def filter_files(files, e_patterns, compile_versions):
145
 
    """Generate (file, versions_to_compile) pairs."""
146
 
    for fn in files:
147
 
        valid_versions = set(compile_versions)  # all by default
148
 
 
149
 
        for type_, vers, dname, pattern in e_patterns:
150
 
            if type_ == 'dir' and fn.startswith(dname):
151
 
                valid_versions = valid_versions - vers
152
 
            elif type_ == 're' and pattern.match(fn):
153
 
                valid_versions = valid_versions - vers
154
 
 
155
 
            # move to the next file if all versions were removed
156
 
            if not valid_versions:
157
 
                break
158
 
        if valid_versions:
159
 
            public_dir = PUBLIC_DIR_RE.match(fn)
160
 
            if public_dir:
161
 
                yield fn, set([getver(public_dir.group(1))])
162
 
            else:
163
 
                yield fn, valid_versions
164
 
 
165
 
 
166
 
### COMPILE ####################################################
167
 
def py_compile(version, workers):
168
 
    if not isinstance(version, basestring):
169
 
        version = vrepr(version)
170
 
    cmd = "python%s -m py_compile -" % version
171
 
    process = Popen(cmd, bufsize=1, shell=True,
172
 
                    stdin=PIPE, close_fds=True)
173
 
    workers[version] = process  # keep the reference for .communicate()
174
 
    stdin = process.stdin
175
 
    while True:
176
 
        filename = (yield)
177
 
        stdin.write(filename + '\n')
178
 
 
179
 
 
180
 
def compile(files, versions, e_patterns=None):
181
 
    global STDINS, WORKERS
182
 
    # start Python interpreters that will handle byte compilation
183
 
    for version in versions:
184
 
        if version not in STDINS:
185
 
            coroutine = py_compile(version, WORKERS)
186
 
            coroutine.next()
187
 
            STDINS[version] = coroutine
188
 
 
189
 
    # byte compile files
190
 
    for fn, versions_to_compile in filter_files(files, e_patterns, versions):
191
 
        if exists("%sc" % fn):
192
 
            continue
193
 
        for version in versions_to_compile:
194
 
            pipe = STDINS[version]
195
 
            pipe.send(fn)
196
 
 
197
 
 
198
 
################################################################
199
 
def main():
200
 
    usage = '%prog [-V [X.Y][-][A.B]] DIR_OR_FILE [-X REGEXPR]\n' + \
201
 
     '       %prog -p PACKAGE'
202
 
    parser = optparse.OptionParser(usage, version='%prog 0.9',
203
 
                                   option_class=Option)
204
 
    parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
205
 
        help='turn verbose mode on')
206
 
    parser.add_option('-q', '--quiet', action='store_false', dest='verbose',
207
 
        default=False, help='be quiet')
208
 
    parser.add_option('-p', '--package',
209
 
        help='specify Debian package name whose files should be bytecompiled')
210
 
    parser.add_option('-V', type='version_range', dest='vrange',
211
 
        help="""force private modules to be bytecompiled with Python version
212
 
from given range, regardless of the default Python version in the system.
213
 
If there are no other options, bytecompile all public modules for installed
214
 
Python versions that match given range.
215
 
 
216
 
VERSION_RANGE examples: '2.5' (version 2.5 only), '2.5-' (version 2.5 or
217
 
newer), '2.5-2.7' (version 2.5 or 2.6), '-3.0' (all supported 2.X versions)""")
218
 
    parser.add_option('-X', '--exclude', action='append',
219
 
        dest='regexpr', type='regexpr',
220
 
        help='exclude items that match given REGEXPR. You may use this option \
221
 
multiple times to build up a list of things to exclude.')
222
 
 
223
 
    (options, args) = parser.parse_args()
224
 
 
225
 
    if options.verbose or environ.get('PYCOMPILE_DEBUG') == '1':
226
 
        log.setLevel(logging.DEBUG)
227
 
        log.debug('argv: %s', sys.argv)
228
 
        log.debug('options: %s', options)
229
 
        log.debug('args: %s', args)
230
 
    else:
231
 
        log.setLevel(logging.WARN)
232
 
 
233
 
    if options.regexpr and not args:
234
 
        parser.error('--exclude option works with private directories '
235
 
            'only, please use /usr/share/python/bcep to specify '
236
 
            'public modules to skip')
237
 
 
238
 
    versions = get_requested_versions(options.vrange, available=True)
239
 
    if not versions:
240
 
        log.error('Requested versions are not installed')
241
 
        exit(3)
242
 
 
243
 
    if options.package and args:  # package's private directories
244
 
        # get requested Python version
245
 
        compile_versions = debsorted(versions)[:1]
246
 
        log.debug('compile versions: %s', versions)
247
 
 
248
 
        pkg_files = tuple(get_package_files(options.package))
249
 
        for item in args:
250
 
            e_patterns = get_exclude_patterns(item, options.regexpr, \
251
 
                                              compile_versions)
252
 
            if not exists(item):
253
 
                log.warn('No such file or directory: %s', item)
254
 
            else:
255
 
                log.debug('byte compiling %s using Python %s',
256
 
                          item, compile_versions)
257
 
                files = get_private_files(pkg_files, item)
258
 
                compile(files, compile_versions, e_patterns)
259
 
    elif options.package:  # package's public modules
260
 
        # no need to limit versions here, it's either pyr mode or version is
261
 
        # hardcoded in path / via -V option
262
 
        e_patterns = get_exclude_patterns()
263
 
        files = get_package_files(options.package)
264
 
        files = get_public_files(files, versions)
265
 
        compile(files, versions, e_patterns)
266
 
    elif args:  # other directories/files (public ones mostly)
267
 
        versions = debsorted(versions)[:1]
268
 
        for item in args:
269
 
            e_patterns = get_exclude_patterns(item, options.regexpr, versions)
270
 
            files = get_directory_files(item)
271
 
            compile(files, versions, e_patterns)
272
 
    else:
273
 
        parser.print_usage()
274
 
        exit(1)
275
 
 
276
 
    # wait for all processes to finish
277
 
    for process in WORKERS.itervalues():
278
 
        process.communicate()
279
 
 
280
 
if __name__ == '__main__':
281
 
    main()