mirror of
https://github.com/tcsenpai/UWINE.git
synced 2025-06-17 08:50:01 +00:00
320 lines
13 KiB
Python
Executable File
320 lines
13 KiB
Python
Executable File
#! /usr/bin/python3
|
|
# vim: et ts=4 sw=4
|
|
|
|
# Copyright © 2010-2012 Piotr Ożarowski <piotr@debian.org>
|
|
# Copyright © 2010 Canonical Ltd
|
|
#
|
|
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
# of this software and associated documentation files (the "Software"), to deal
|
|
# in the Software without restriction, including without limitation the rights
|
|
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
# copies of the Software, and to permit persons to whom the Software is
|
|
# furnished to do so, subject to the following conditions:
|
|
#
|
|
# The above copyright notice and this permission notice shall be included in
|
|
# all copies or substantial portions of the Software.
|
|
#
|
|
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
# THE SOFTWARE.
|
|
|
|
import logging
|
|
import optparse
|
|
import os
|
|
import struct
|
|
import sys
|
|
from os import environ, listdir, mkdir
|
|
from os.path import dirname, exists, isdir, join
|
|
from subprocess import PIPE, Popen
|
|
sys.path.insert(1, '/usr/share/python3/')
|
|
from debpython.version import SUPPORTED, debsorted, vrepr, \
|
|
get_requested_versions, parse_vrange, getver
|
|
from debpython import files as dpf, PUBLIC_DIR_RE, memoize
|
|
from debpython.interpreter import Interpreter
|
|
from debpython.option import Option, compile_regexpr
|
|
|
|
# initialize script
|
|
logging.basicConfig(format='%(levelname).1s: %(module)s:%(lineno)d: '
|
|
'%(message)s')
|
|
log = logging.getLogger(__name__)
|
|
STDINS = {}
|
|
WORKERS = {}
|
|
|
|
"""TODO: move it to manpage
|
|
Examples:
|
|
pycompile -p python3-mako # package's public files
|
|
pycompile -p python3-foo /usr/share/foo # package's private files
|
|
pycompile -V 3.1 /usr/lib/python3.1/ # python3.1 only
|
|
pycompile -V 3.1 /usr/lib/foo/bar.py # python3.1 only
|
|
pycompile -V 3.2- /usr/lib/python3/
|
|
"""
|
|
|
|
|
|
### EXCLUDES ###################################################
|
|
@memoize
|
|
def get_exclude_patterns_from_dir(name='/usr/share/python3/bcep/'):
|
|
"""Return patterns for files that shouldn't be bytecompiled."""
|
|
if not isdir(name):
|
|
return []
|
|
|
|
result = []
|
|
for fn in listdir(name):
|
|
if fn.startswith('.'):
|
|
continue
|
|
with open(join(name, fn), 'r', encoding='utf-8') as lines:
|
|
for line in lines:
|
|
if line.startswith('#'):
|
|
continue
|
|
line = line.rstrip('\n')
|
|
|
|
try:
|
|
type_, vrange, dname, pattern = line.split('|', 3)
|
|
except ValueError:
|
|
pattern = '.*'
|
|
type_, vrange, dname = line.split('|', 2)
|
|
if type_ != 'file':
|
|
dname = dname.rstrip('/') + '/' # make sure it ends with slash
|
|
vrange = parse_vrange(vrange)
|
|
|
|
versions = get_requested_versions(vrange, available=True)
|
|
if not versions:
|
|
# pattern doesn't match installed Python versions
|
|
continue
|
|
|
|
if type_ == 're' or pattern:
|
|
try:
|
|
pattern = compile_regexpr(None, None, pattern)
|
|
except Exception:
|
|
log.warning('skipping invalid pattern in file %s, line: %s', fn, line)
|
|
continue
|
|
|
|
result.append((type_, versions, dname, pattern))
|
|
return result
|
|
|
|
|
|
def get_exclude_patterns(directory='/', patterns=None, versions=None):
|
|
"""Return patterns for files that shouldn't be compiled in given dir."""
|
|
|
|
# make sure directory name ends with a slash
|
|
directory = directory.rstrip('/') + '/'
|
|
|
|
if versions is not None:
|
|
# make sure it's a set (debsorted returns a list)
|
|
versions = set(versions)
|
|
if patterns:
|
|
if versions is None:
|
|
versions = set(SUPPORTED)
|
|
patterns = [('re', versions, directory, i) for i in patterns]
|
|
else:
|
|
patterns = []
|
|
|
|
for type_, vers, dname, pattern in get_exclude_patterns_from_dir():
|
|
# skip patterns that do not match requested directory
|
|
if not dname.startswith(directory[:len(dname)]):
|
|
continue
|
|
# skip patterns that do not match requested versions
|
|
if versions and not versions & vers:
|
|
continue
|
|
patterns.append((type_, vers, dname, pattern))
|
|
return patterns
|
|
|
|
|
|
def filter_files(files, e_patterns, compile_versions):
|
|
"""Generate (file, versions_to_compile) pairs."""
|
|
for fpath in files:
|
|
valid_versions = set(compile_versions) # all by default
|
|
|
|
for type_, vers, dname, pattern in e_patterns:
|
|
if not fpath.startswith(dname):
|
|
continue
|
|
rfpath = fpath[len(dname):] # relative to dname
|
|
|
|
if type_ == 'dir': # fpath.startswith(dname) - see above
|
|
valid_versions = valid_versions - vers
|
|
elif type_ == 'file' and fpath == dname:
|
|
valid_versions = valid_versions - vers
|
|
elif type_ == 're' and (pattern.match(fpath) or pattern.match(rfpath)):
|
|
# NOTE: directory is checked at the beginning of this loop
|
|
valid_versions = valid_versions - vers
|
|
|
|
# move to the next file if all versions were removed
|
|
if not valid_versions:
|
|
break
|
|
if valid_versions:
|
|
public_dir = PUBLIC_DIR_RE.match(fpath)
|
|
if public_dir and len(public_dir.group(1)) != 1:
|
|
yield fpath, set([getver(public_dir.group(1))])
|
|
else:
|
|
yield fpath, valid_versions
|
|
|
|
|
|
### COMPILE ####################################################
|
|
def py_compile(version, optimize, workers):
|
|
if not isinstance(version, str):
|
|
version = vrepr(version)
|
|
cmd = "/usr/bin/python%s%s -m py_compile -" \
|
|
% (version, ' -O' if optimize else '')
|
|
process = Popen(cmd, bufsize=0, shell=True,
|
|
stdin=PIPE, close_fds=True)
|
|
workers[version] = process # keep the reference for .communicate()
|
|
stdin = process.stdin
|
|
while True:
|
|
filename = (yield)
|
|
stdin.write(filename.encode('utf-8') + b'\n')
|
|
|
|
|
|
def compile(files, versions, force, optimize, e_patterns=None):
|
|
global STDINS, WORKERS
|
|
# start Python interpreters that will handle byte compilation
|
|
for version in versions:
|
|
if version not in STDINS:
|
|
coroutine = py_compile(version, optimize, WORKERS)
|
|
next(coroutine)
|
|
STDINS[version] = coroutine
|
|
|
|
interpreter = Interpreter('python' if not optimize else 'python -O')
|
|
|
|
# byte compile files
|
|
skip_dirs = set()
|
|
for fn, versions_to_compile in filter_files(files, e_patterns, versions):
|
|
for version in versions_to_compile:
|
|
cfn = interpreter.cache_file(fn, version)
|
|
if version == (3, 1):
|
|
if exists(cfn) and not force:
|
|
ftime = os.stat(fn).st_mtime
|
|
try:
|
|
ctime = os.stat(cfn).st_mtime
|
|
except os.error:
|
|
ctime = 0
|
|
if ctime > ftime:
|
|
continue
|
|
else:
|
|
pycache_dir = dirname(cfn)
|
|
if not force:
|
|
try:
|
|
mtime = int(os.stat(fn).st_mtime)
|
|
expect = struct.pack('<4sl',
|
|
interpreter.magic_number(version), mtime)
|
|
with open(cfn, 'rb') as chandle:
|
|
actual = chandle.read(8)
|
|
if expect == actual:
|
|
continue
|
|
except (IOError, OSError):
|
|
pass
|
|
if pycache_dir not in skip_dirs and not exists(pycache_dir):
|
|
try:
|
|
mkdir(pycache_dir)
|
|
except Exception as e:
|
|
log.error("cannot create directory %s: %r", pycache_dir, e)
|
|
skip_dirs.add(pycache_dir)
|
|
continue
|
|
pipe = STDINS[version]
|
|
pipe.send(fn)
|
|
|
|
|
|
################################################################
|
|
def main():
|
|
usage = '%prog [-V [X.Y][-][A.B]] DIR_OR_FILE [-X REGEXPR]\n' +\
|
|
' %prog -p PACKAGE'
|
|
parser = optparse.OptionParser(usage, version='%prog 3.9.2-3',
|
|
option_class=Option)
|
|
parser.add_option('-v', '--verbose', action='store_true', dest='verbose',
|
|
help='turn verbose mode on')
|
|
parser.add_option('-q', '--quiet', action='store_false', dest='verbose',
|
|
default=False, help='be quiet')
|
|
parser.add_option('-f', '--force', action='store_true', dest='force',
|
|
default=False,
|
|
help='force rebuild even if timestamps are up-to-date')
|
|
parser.add_option('-O', action='store_true', dest='optimize',
|
|
default=False, help="byte-compile to .pyo files")
|
|
parser.add_option('-p', '--package',
|
|
help='specify Debian package name whose files should be bytecompiled')
|
|
parser.add_option('-V', type='version_range', dest='vrange',
|
|
help="""force private modules to be bytecompiled
|
|
with Python version from given range, regardless of the default Python version
|
|
in the system. If there are no other options, bytecompile all public modules
|
|
for installed Python versions that match given range.
|
|
|
|
VERSION_RANGE examples: '3.1' (version 3.1 only), '3.1-' (version 3.1 or
|
|
newer), '3.1-3.3' (version 3.1 or 3.2), '-4.0' (all supported 3.X versions)""")
|
|
parser.add_option('-X', '--exclude', action='append',
|
|
dest='regexpr', type='regexpr',
|
|
help='exclude items that match given REGEXPR. \
|
|
You may use this option multiple times to build up a list of things to exclude.')
|
|
|
|
(options, args) = parser.parse_args()
|
|
|
|
if options.verbose or environ.get('PYCOMPILE_DEBUG') == '1':
|
|
log.setLevel(logging.DEBUG)
|
|
log.debug('argv: %s', sys.argv)
|
|
log.debug('options: %s', options)
|
|
log.debug('args: %s', args)
|
|
else:
|
|
log.setLevel(logging.WARN)
|
|
|
|
if options.regexpr and not args:
|
|
parser.error('--exclude option works with private directories '
|
|
'only, please use /usr/share/python3/bcep to specify '
|
|
'public modules to skip')
|
|
|
|
if options.vrange and options.vrange[0] == options.vrange[1] and\
|
|
options.vrange != (None, None) and\
|
|
exists("/usr/bin/python%d.%d" % options.vrange[0]):
|
|
# specific version requested, use it even if it's not in SUPPORTED
|
|
versions = {options.vrange[0]}
|
|
else:
|
|
versions = get_requested_versions(options.vrange, available=True)
|
|
if not versions:
|
|
log.error('Requested versions are not installed')
|
|
exit(3)
|
|
|
|
if options.package and args: # package's private directories
|
|
# get requested Python version
|
|
compile_versions = debsorted(versions)[:1]
|
|
log.debug('compile versions: %s', versions)
|
|
|
|
pkg_files = tuple(dpf.from_package(options.package))
|
|
for item in args:
|
|
e_patterns = get_exclude_patterns(item, options.regexpr,
|
|
compile_versions)
|
|
if not exists(item):
|
|
log.warning('No such file or directory: %s', item)
|
|
else:
|
|
log.debug('byte compiling %s using Python %s',
|
|
item, compile_versions)
|
|
files = dpf.filter_directory(pkg_files, item)
|
|
compile(files, compile_versions, options.force,
|
|
options.optimize, e_patterns)
|
|
elif options.package: # package's public modules
|
|
# no need to limit versions here, it's either pyr mode or version is
|
|
# hardcoded in path / via -V option
|
|
e_patterns = get_exclude_patterns()
|
|
files = dpf.from_package(options.package)
|
|
files = dpf.filter_public(files, versions)
|
|
compile(files, versions,
|
|
options.force, options.optimize, e_patterns)
|
|
elif args: # other directories/files
|
|
for item in args:
|
|
e_patterns = get_exclude_patterns(item, options.regexpr, versions)
|
|
files = dpf.from_directory(item)
|
|
compile(files, versions,
|
|
options.force, options.optimize, e_patterns)
|
|
else:
|
|
parser.print_usage()
|
|
exit(1)
|
|
|
|
# wait for all processes to finish
|
|
rv = 0
|
|
for process in WORKERS.values():
|
|
process.communicate()
|
|
if process.returncode not in (None, 0):
|
|
rv = process.returncode
|
|
exit(rv)
|
|
|
|
if __name__ == '__main__':
|
|
main()
|