| Index: third_party/cython/src/Cython/Build/Dependencies.py
|
| diff --git a/third_party/cython/src/Cython/Build/Dependencies.py b/third_party/cython/src/Cython/Build/Dependencies.py
|
| new file mode 100644
|
| index 0000000000000000000000000000000000000000..467376a06466f56c337f4cd6e7053fd9015cdecb
|
| --- /dev/null
|
| +++ b/third_party/cython/src/Cython/Build/Dependencies.py
|
| @@ -0,0 +1,959 @@
|
| +import cython
|
| +from Cython import __version__
|
| +
|
| +import re, os, sys, time
|
| +try:
|
| + from glob import iglob
|
| +except ImportError:
|
| + # Py2.4
|
| + from glob import glob as iglob
|
| +
|
| +try:
|
| + import gzip
|
| + gzip_open = gzip.open
|
| + gzip_ext = '.gz'
|
| +except ImportError:
|
| + gzip_open = open
|
| + gzip_ext = ''
|
| +import shutil
|
| +import subprocess
|
| +
|
| +try:
|
| + import hashlib
|
| +except ImportError:
|
| + import md5 as hashlib
|
| +
|
| +try:
|
| + from io import open as io_open
|
| +except ImportError:
|
| + from codecs import open as io_open
|
| +
|
| +try:
|
| + from os.path import relpath as _relpath
|
| +except ImportError:
|
| + # Py<2.6
|
| + def _relpath(path, start=os.path.curdir):
|
| + if not path:
|
| + raise ValueError("no path specified")
|
| + start_list = os.path.abspath(start).split(os.path.sep)
|
| + path_list = os.path.abspath(path).split(os.path.sep)
|
| + i = len(os.path.commonprefix([start_list, path_list]))
|
| + rel_list = [os.path.pardir] * (len(start_list)-i) + path_list[i:]
|
| + if not rel_list:
|
| + return os.path.curdir
|
| + return os.path.join(*rel_list)
|
| +
|
| +
|
| +from distutils.extension import Extension
|
| +
|
| +from Cython import Utils
|
| +from Cython.Utils import cached_function, cached_method, path_exists, find_root_package_dir
|
| +from Cython.Compiler.Main import Context, CompilationOptions, default_options
|
| +
|
| +join_path = cached_function(os.path.join)
|
| +
|
| +if sys.version_info[0] < 3:
|
| + # stupid Py2 distutils enforces str type in list of sources
|
| + _fs_encoding = sys.getfilesystemencoding()
|
| + if _fs_encoding is None:
|
| + _fs_encoding = sys.getdefaultencoding()
|
| + def encode_filename_in_py2(filename):
|
| + if isinstance(filename, unicode):
|
| + return filename.encode(_fs_encoding)
|
| + return filename
|
| +else:
|
| + def encode_filename_in_py2(filename):
|
| + return filename
|
| + basestring = str
|
| +
|
| +def extended_iglob(pattern):
|
| + if '**/' in pattern:
|
| + seen = set()
|
| + first, rest = pattern.split('**/', 1)
|
| + if first:
|
| + first = iglob(first+'/')
|
| + else:
|
| + first = ['']
|
| + for root in first:
|
| + for path in extended_iglob(join_path(root, rest)):
|
| + if path not in seen:
|
| + seen.add(path)
|
| + yield path
|
| + for path in extended_iglob(join_path(root, '*', '**/' + rest)):
|
| + if path not in seen:
|
| + seen.add(path)
|
| + yield path
|
| + else:
|
| + for path in iglob(pattern):
|
| + yield path
|
| +
|
| +@cached_function
|
| +def file_hash(filename):
|
| + path = os.path.normpath(filename.encode("UTF-8"))
|
| + m = hashlib.md5(str(len(path)) + ":")
|
| + m.update(path)
|
| + f = open(filename, 'rb')
|
| + try:
|
| + data = f.read(65000)
|
| + while data:
|
| + m.update(data)
|
| + data = f.read(65000)
|
| + finally:
|
| + f.close()
|
| + return m.hexdigest()
|
| +
|
| +def parse_list(s):
|
| + """
|
| + >>> parse_list("a b c")
|
| + ['a', 'b', 'c']
|
| + >>> parse_list("[a, b, c]")
|
| + ['a', 'b', 'c']
|
| + >>> parse_list('a " " b')
|
| + ['a', ' ', 'b']
|
| + >>> parse_list('[a, ",a", "a,", ",", ]')
|
| + ['a', ',a', 'a,', ',']
|
| + """
|
| + if s[0] == '[' and s[-1] == ']':
|
| + s = s[1:-1]
|
| + delimiter = ','
|
| + else:
|
| + delimiter = ' '
|
| + s, literals = strip_string_literals(s)
|
| + def unquote(literal):
|
| + literal = literal.strip()
|
| + if literal[0] in "'\"":
|
| + return literals[literal[1:-1]]
|
| + else:
|
| + return literal
|
| + return [unquote(item) for item in s.split(delimiter) if item.strip()]
|
| +
|
| +transitive_str = object()
|
| +transitive_list = object()
|
| +
|
| +distutils_settings = {
|
| + 'name': str,
|
| + 'sources': list,
|
| + 'define_macros': list,
|
| + 'undef_macros': list,
|
| + 'libraries': transitive_list,
|
| + 'library_dirs': transitive_list,
|
| + 'runtime_library_dirs': transitive_list,
|
| + 'include_dirs': transitive_list,
|
| + 'extra_objects': list,
|
| + 'extra_compile_args': transitive_list,
|
| + 'extra_link_args': transitive_list,
|
| + 'export_symbols': list,
|
| + 'depends': transitive_list,
|
| + 'language': transitive_str,
|
| +}
|
| +
|
| +@cython.locals(start=long, end=long)
|
| +def line_iter(source):
|
| + if isinstance(source, basestring):
|
| + start = 0
|
| + while True:
|
| + end = source.find('\n', start)
|
| + if end == -1:
|
| + yield source[start:]
|
| + return
|
| + yield source[start:end]
|
| + start = end+1
|
| + else:
|
| + for line in source:
|
| + yield line
|
| +
|
| +class DistutilsInfo(object):
|
| +
|
| + def __init__(self, source=None, exn=None):
|
| + self.values = {}
|
| + if source is not None:
|
| + for line in line_iter(source):
|
| + line = line.strip()
|
| + if line != '' and line[0] != '#':
|
| + break
|
| + line = line[1:].strip()
|
| + if line[:10] == 'distutils:':
|
| + line = line[10:]
|
| + ix = line.index('=')
|
| + key = str(line[:ix].strip())
|
| + value = line[ix+1:].strip()
|
| + type = distutils_settings[key]
|
| + if type in (list, transitive_list):
|
| + value = parse_list(value)
|
| + if key == 'define_macros':
|
| + value = [tuple(macro.split('=')) for macro in value]
|
| + self.values[key] = value
|
| + elif exn is not None:
|
| + for key in distutils_settings:
|
| + if key in ('name', 'sources'):
|
| + continue
|
| + value = getattr(exn, key, None)
|
| + if value:
|
| + self.values[key] = value
|
| +
|
| + def merge(self, other):
|
| + if other is None:
|
| + return self
|
| + for key, value in other.values.items():
|
| + type = distutils_settings[key]
|
| + if type is transitive_str and key not in self.values:
|
| + self.values[key] = value
|
| + elif type is transitive_list:
|
| + if key in self.values:
|
| + all = self.values[key]
|
| + for v in value:
|
| + if v not in all:
|
| + all.append(v)
|
| + else:
|
| + self.values[key] = value
|
| + return self
|
| +
|
| + def subs(self, aliases):
|
| + if aliases is None:
|
| + return self
|
| + resolved = DistutilsInfo()
|
| + for key, value in self.values.items():
|
| + type = distutils_settings[key]
|
| + if type in [list, transitive_list]:
|
| + new_value_list = []
|
| + for v in value:
|
| + if v in aliases:
|
| + v = aliases[v]
|
| + if isinstance(v, list):
|
| + new_value_list += v
|
| + else:
|
| + new_value_list.append(v)
|
| + value = new_value_list
|
| + else:
|
| + if value in aliases:
|
| + value = aliases[value]
|
| + resolved.values[key] = value
|
| + return resolved
|
| +
|
| + def apply(self, extension):
|
| + for key, value in self.values.items():
|
| + type = distutils_settings[key]
|
| + if type in [list, transitive_list]:
|
| + getattr(extension, key).extend(value)
|
| + else:
|
| + setattr(extension, key, value)
|
| +
|
| +@cython.locals(start=long, q=long, single_q=long, double_q=long, hash_mark=long,
|
| + end=long, k=long, counter=long, quote_len=long)
|
| +def strip_string_literals(code, prefix='__Pyx_L'):
|
| + """
|
| + Normalizes every string literal to be of the form '__Pyx_Lxxx',
|
| + returning the normalized code and a mapping of labels to
|
| + string literals.
|
| + """
|
| + new_code = []
|
| + literals = {}
|
| + counter = 0
|
| + start = q = 0
|
| + in_quote = False
|
| + hash_mark = single_q = double_q = -1
|
| + code_len = len(code)
|
| +
|
| + while True:
|
| + if hash_mark < q:
|
| + hash_mark = code.find('#', q)
|
| + if single_q < q:
|
| + single_q = code.find("'", q)
|
| + if double_q < q:
|
| + double_q = code.find('"', q)
|
| + q = min(single_q, double_q)
|
| + if q == -1: q = max(single_q, double_q)
|
| +
|
| + # We're done.
|
| + if q == -1 and hash_mark == -1:
|
| + new_code.append(code[start:])
|
| + break
|
| +
|
| + # Try to close the quote.
|
| + elif in_quote:
|
| + if code[q-1] == u'\\':
|
| + k = 2
|
| + while q >= k and code[q-k] == u'\\':
|
| + k += 1
|
| + if k % 2 == 0:
|
| + q += 1
|
| + continue
|
| + if code[q] == quote_type and (quote_len == 1 or (code_len > q + 2 and quote_type == code[q+1] == code[q+2])):
|
| + counter += 1
|
| + label = "%s%s_" % (prefix, counter)
|
| + literals[label] = code[start+quote_len:q]
|
| + full_quote = code[q:q+quote_len]
|
| + new_code.append(full_quote)
|
| + new_code.append(label)
|
| + new_code.append(full_quote)
|
| + q += quote_len
|
| + in_quote = False
|
| + start = q
|
| + else:
|
| + q += 1
|
| +
|
| + # Process comment.
|
| + elif -1 != hash_mark and (hash_mark < q or q == -1):
|
| + new_code.append(code[start:hash_mark+1])
|
| + end = code.find('\n', hash_mark)
|
| + counter += 1
|
| + label = "%s%s_" % (prefix, counter)
|
| + if end == -1:
|
| + end_or_none = None
|
| + else:
|
| + end_or_none = end
|
| + literals[label] = code[hash_mark+1:end_or_none]
|
| + new_code.append(label)
|
| + if end == -1:
|
| + break
|
| + start = q = end
|
| +
|
| + # Open the quote.
|
| + else:
|
| + if code_len >= q+3 and (code[q] == code[q+1] == code[q+2]):
|
| + quote_len = 3
|
| + else:
|
| + quote_len = 1
|
| + in_quote = True
|
| + quote_type = code[q]
|
| + new_code.append(code[start:q])
|
| + start = q
|
| + q += quote_len
|
| +
|
| + return "".join(new_code), literals
|
| +
|
| +
|
| +dependancy_regex = re.compile(r"(?:^from +([0-9a-zA-Z_.]+) +cimport)|"
|
| + r"(?:^cimport +([0-9a-zA-Z_.]+)\b)|"
|
| + r"(?:^cdef +extern +from +['\"]([^'\"]+)['\"])|"
|
| + r"(?:^include +['\"]([^'\"]+)['\"])", re.M)
|
| +
|
| +def normalize_existing(base_path, rel_paths):
|
| + return normalize_existing0(os.path.dirname(base_path), tuple(set(rel_paths)))
|
| +
|
| +@cached_function
|
| +def normalize_existing0(base_dir, rel_paths):
|
| + normalized = []
|
| + for rel in rel_paths:
|
| + path = join_path(base_dir, rel)
|
| + if path_exists(path):
|
| + normalized.append(os.path.normpath(path))
|
| + else:
|
| + normalized.append(rel)
|
| + return normalized
|
| +
|
| +def resolve_depends(depends, include_dirs):
|
| + include_dirs = tuple(include_dirs)
|
| + resolved = []
|
| + for depend in depends:
|
| + path = resolve_depend(depend, include_dirs)
|
| + if path is not None:
|
| + resolved.append(path)
|
| + return resolved
|
| +
|
| +@cached_function
|
| +def resolve_depend(depend, include_dirs):
|
| + if depend[0] == '<' and depend[-1] == '>':
|
| + return None
|
| + for dir in include_dirs:
|
| + path = join_path(dir, depend)
|
| + if path_exists(path):
|
| + return os.path.normpath(path)
|
| + return None
|
| +
|
| +@cached_function
|
| +def package(filename):
|
| + dir = os.path.dirname(os.path.abspath(str(filename)))
|
| + if dir != filename and path_exists(join_path(dir, '__init__.py')):
|
| + return package(dir) + (os.path.basename(dir),)
|
| + else:
|
| + return ()
|
| +
|
| +@cached_function
|
| +def fully_qualified_name(filename):
|
| + module = os.path.splitext(os.path.basename(filename))[0]
|
| + return '.'.join(package(filename) + (module,))
|
| +
|
| +
|
| +@cached_function
|
| +def parse_dependencies(source_filename):
|
| + # Actual parsing is way to slow, so we use regular expressions.
|
| + # The only catch is that we must strip comments and string
|
| + # literals ahead of time.
|
| + fh = Utils.open_source_file(source_filename, "rU", error_handling='ignore')
|
| + try:
|
| + source = fh.read()
|
| + finally:
|
| + fh.close()
|
| + distutils_info = DistutilsInfo(source)
|
| + source, literals = strip_string_literals(source)
|
| + source = source.replace('\\\n', ' ').replace('\t', ' ')
|
| +
|
| + # TODO: pure mode
|
| + cimports = []
|
| + includes = []
|
| + externs = []
|
| + for m in dependancy_regex.finditer(source):
|
| + cimport_from, cimport, extern, include = m.groups()
|
| + if cimport_from:
|
| + cimports.append(cimport_from)
|
| + elif cimport:
|
| + cimports.append(cimport)
|
| + elif extern:
|
| + externs.append(literals[extern])
|
| + else:
|
| + includes.append(literals[include])
|
| + return cimports, includes, externs, distutils_info
|
| +
|
| +
|
| +class DependencyTree(object):
|
| +
|
| + def __init__(self, context, quiet=False):
|
| + self.context = context
|
| + self.quiet = quiet
|
| + self._transitive_cache = {}
|
| +
|
| + def parse_dependencies(self, source_filename):
|
| + return parse_dependencies(source_filename)
|
| +
|
| + @cached_method
|
| + def included_files(self, filename):
|
| + # This is messy because included files are textually included, resolving
|
| + # cimports (but not includes) relative to the including file.
|
| + all = set()
|
| + for include in self.parse_dependencies(filename)[1]:
|
| + include_path = join_path(os.path.dirname(filename), include)
|
| + if not path_exists(include_path):
|
| + include_path = self.context.find_include_file(include, None)
|
| + if include_path:
|
| + if '.' + os.path.sep in include_path:
|
| + include_path = os.path.normpath(include_path)
|
| + all.add(include_path)
|
| + all.update(self.included_files(include_path))
|
| + elif not self.quiet:
|
| + print("Unable to locate '%s' referenced from '%s'" % (filename, include))
|
| + return all
|
| +
|
| + @cached_method
|
| + def cimports_and_externs(self, filename):
|
| + # This is really ugly. Nested cimports are resolved with respect to the
|
| + # includer, but includes are resolved with respect to the includee.
|
| + cimports, includes, externs = self.parse_dependencies(filename)[:3]
|
| + cimports = set(cimports)
|
| + externs = set(externs)
|
| + for include in self.included_files(filename):
|
| + included_cimports, included_externs = self.cimports_and_externs(include)
|
| + cimports.update(included_cimports)
|
| + externs.update(included_externs)
|
| + return tuple(cimports), normalize_existing(filename, externs)
|
| +
|
| + def cimports(self, filename):
|
| + return self.cimports_and_externs(filename)[0]
|
| +
|
| + def package(self, filename):
|
| + return package(filename)
|
| +
|
| + def fully_qualified_name(self, filename):
|
| + return fully_qualified_name(filename)
|
| +
|
| + @cached_method
|
| + def find_pxd(self, module, filename=None):
|
| + is_relative = module[0] == '.'
|
| + if is_relative and not filename:
|
| + raise NotImplementedError("New relative imports.")
|
| + if filename is not None:
|
| + module_path = module.split('.')
|
| + if is_relative:
|
| + module_path.pop(0) # just explicitly relative
|
| + package_path = list(self.package(filename))
|
| + while module_path and not module_path[0]:
|
| + try:
|
| + package_path.pop()
|
| + except IndexError:
|
| + return None # FIXME: error?
|
| + module_path.pop(0)
|
| + relative = '.'.join(package_path + module_path)
|
| + pxd = self.context.find_pxd_file(relative, None)
|
| + if pxd:
|
| + return pxd
|
| + if is_relative:
|
| + return None # FIXME: error?
|
| + return self.context.find_pxd_file(module, None)
|
| +
|
| + @cached_method
|
| + def cimported_files(self, filename):
|
| + if filename[-4:] == '.pyx' and path_exists(filename[:-4] + '.pxd'):
|
| + pxd_list = [filename[:-4] + '.pxd']
|
| + else:
|
| + pxd_list = []
|
| + for module in self.cimports(filename):
|
| + if module[:7] == 'cython.' or module == 'cython':
|
| + continue
|
| + pxd_file = self.find_pxd(module, filename)
|
| + if pxd_file is not None:
|
| + pxd_list.append(pxd_file)
|
| + elif not self.quiet:
|
| + print("missing cimport in module '%s': %s" % (module, filename))
|
| + return tuple(pxd_list)
|
| +
|
| + @cached_method
|
| + def immediate_dependencies(self, filename):
|
| + all = set([filename])
|
| + all.update(self.cimported_files(filename))
|
| + all.update(self.included_files(filename))
|
| + return all
|
| +
|
| + def all_dependencies(self, filename):
|
| + return self.transitive_merge(filename, self.immediate_dependencies, set.union)
|
| +
|
| + @cached_method
|
| + def timestamp(self, filename):
|
| + return os.path.getmtime(filename)
|
| +
|
| + def extract_timestamp(self, filename):
|
| + return self.timestamp(filename), filename
|
| +
|
| + def newest_dependency(self, filename):
|
| + return max([self.extract_timestamp(f) for f in self.all_dependencies(filename)])
|
| +
|
| + def transitive_fingerprint(self, filename, extra=None):
|
| + try:
|
| + m = hashlib.md5(__version__)
|
| + m.update(file_hash(filename))
|
| + for x in sorted(self.all_dependencies(filename)):
|
| + if os.path.splitext(x)[1] not in ('.c', '.cpp', '.h'):
|
| + m.update(file_hash(x))
|
| + if extra is not None:
|
| + m.update(str(extra))
|
| + return m.hexdigest()
|
| + except IOError:
|
| + return None
|
| +
|
| + def distutils_info0(self, filename):
|
| + info = self.parse_dependencies(filename)[3]
|
| + externs = self.cimports_and_externs(filename)[1]
|
| + if externs:
|
| + if 'depends' in info.values:
|
| + info.values['depends'] = list(set(info.values['depends']).union(externs))
|
| + else:
|
| + info.values['depends'] = list(externs)
|
| + return info
|
| +
|
| + def distutils_info(self, filename, aliases=None, base=None):
|
| + return (self.transitive_merge(filename, self.distutils_info0, DistutilsInfo.merge)
|
| + .subs(aliases)
|
| + .merge(base))
|
| +
|
| + def transitive_merge(self, node, extract, merge):
|
| + try:
|
| + seen = self._transitive_cache[extract, merge]
|
| + except KeyError:
|
| + seen = self._transitive_cache[extract, merge] = {}
|
| + return self.transitive_merge_helper(
|
| + node, extract, merge, seen, {}, self.cimported_files)[0]
|
| +
|
| + def transitive_merge_helper(self, node, extract, merge, seen, stack, outgoing):
|
| + if node in seen:
|
| + return seen[node], None
|
| + deps = extract(node)
|
| + if node in stack:
|
| + return deps, node
|
| + try:
|
| + stack[node] = len(stack)
|
| + loop = None
|
| + for next in outgoing(node):
|
| + sub_deps, sub_loop = self.transitive_merge_helper(next, extract, merge, seen, stack, outgoing)
|
| + if sub_loop is not None:
|
| + if loop is not None and stack[loop] < stack[sub_loop]:
|
| + pass
|
| + else:
|
| + loop = sub_loop
|
| + deps = merge(deps, sub_deps)
|
| + if loop == node:
|
| + loop = None
|
| + if loop is None:
|
| + seen[node] = deps
|
| + return deps, loop
|
| + finally:
|
| + del stack[node]
|
| +
|
| +_dep_tree = None
|
| +def create_dependency_tree(ctx=None, quiet=False):
|
| + global _dep_tree
|
| + if _dep_tree is None:
|
| + if ctx is None:
|
| + ctx = Context(["."], CompilationOptions(default_options))
|
| + _dep_tree = DependencyTree(ctx, quiet=quiet)
|
| + return _dep_tree
|
| +
|
| +# This may be useful for advanced users?
|
| +def create_extension_list(patterns, exclude=[], ctx=None, aliases=None, quiet=False, exclude_failures=False):
|
| + if not isinstance(patterns, (list, tuple)):
|
| + patterns = [patterns]
|
| + explicit_modules = set([m.name for m in patterns if isinstance(m, Extension)])
|
| + seen = set()
|
| + deps = create_dependency_tree(ctx, quiet=quiet)
|
| + to_exclude = set()
|
| + if not isinstance(exclude, list):
|
| + exclude = [exclude]
|
| + for pattern in exclude:
|
| + to_exclude.update(map(os.path.abspath, extended_iglob(pattern)))
|
| + module_list = []
|
| + for pattern in patterns:
|
| + if isinstance(pattern, str):
|
| + filepattern = pattern
|
| + template = None
|
| + name = '*'
|
| + base = None
|
| + exn_type = Extension
|
| + elif isinstance(pattern, Extension):
|
| + filepattern = pattern.sources[0]
|
| + if os.path.splitext(filepattern)[1] not in ('.py', '.pyx'):
|
| + # ignore non-cython modules
|
| + module_list.append(pattern)
|
| + continue
|
| + template = pattern
|
| + name = template.name
|
| + base = DistutilsInfo(exn=template)
|
| + exn_type = template.__class__
|
| + else:
|
| + raise TypeError(pattern)
|
| + for file in extended_iglob(filepattern):
|
| + if os.path.abspath(file) in to_exclude:
|
| + continue
|
| + pkg = deps.package(file)
|
| + if '*' in name:
|
| + module_name = deps.fully_qualified_name(file)
|
| + if module_name in explicit_modules:
|
| + continue
|
| + else:
|
| + module_name = name
|
| + if module_name not in seen:
|
| + try:
|
| + kwds = deps.distutils_info(file, aliases, base).values
|
| + except Exception:
|
| + if exclude_failures:
|
| + continue
|
| + raise
|
| + if base is not None:
|
| + for key, value in base.values.items():
|
| + if key not in kwds:
|
| + kwds[key] = value
|
| + sources = [file]
|
| + if template is not None:
|
| + sources += template.sources[1:]
|
| + if 'sources' in kwds:
|
| + # allow users to add .c files etc.
|
| + for source in kwds['sources']:
|
| + source = encode_filename_in_py2(source)
|
| + if source not in sources:
|
| + sources.append(source)
|
| + del kwds['sources']
|
| + if 'depends' in kwds:
|
| + depends = resolve_depends(kwds['depends'], (kwds.get('include_dirs') or []) + [find_root_package_dir(file)])
|
| + if template is not None:
|
| + # Always include everything from the template.
|
| + depends = list(set(template.depends).union(set(depends)))
|
| + kwds['depends'] = depends
|
| + module_list.append(exn_type(
|
| + name=module_name,
|
| + sources=sources,
|
| + **kwds))
|
| + m = module_list[-1]
|
| + seen.add(name)
|
| + return module_list
|
| +
|
| +# This is the user-exposed entry point.
|
| +def cythonize(module_list, exclude=[], nthreads=0, aliases=None, quiet=False, force=False,
|
| + exclude_failures=False, **options):
|
| + """
|
| + Compile a set of source modules into C/C++ files and return a list of distutils
|
| + Extension objects for them.
|
| +
|
| + As module list, pass either a glob pattern, a list of glob patterns or a list of
|
| + Extension objects. The latter allows you to configure the extensions separately
|
| + through the normal distutils options.
|
| +
|
| + When using glob patterns, you can exclude certain module names explicitly
|
| + by passing them into the 'exclude' option.
|
| +
|
| + For parallel compilation, set the 'nthreads' option to the number of
|
| + concurrent builds.
|
| +
|
| + For a broad 'try to compile' mode that ignores compilation failures and
|
| + simply excludes the failed extensions, pass 'exclude_failures=True'. Note
|
| + that this only really makes sense for compiling .py files which can also
|
| + be used without compilation.
|
| +
|
| + Additional compilation options can be passed as keyword arguments.
|
| + """
|
| + if 'include_path' not in options:
|
| + options['include_path'] = ['.']
|
| + if 'common_utility_include_dir' in options:
|
| + if options.get('cache'):
|
| + raise NotImplementedError("common_utility_include_dir does not yet work with caching")
|
| + if not os.path.exists(options['common_utility_include_dir']):
|
| + os.makedirs(options['common_utility_include_dir'])
|
| + c_options = CompilationOptions(**options)
|
| + cpp_options = CompilationOptions(**options); cpp_options.cplus = True
|
| + ctx = c_options.create_context()
|
| + options = c_options
|
| + module_list = create_extension_list(
|
| + module_list,
|
| + exclude=exclude,
|
| + ctx=ctx,
|
| + quiet=quiet,
|
| + exclude_failures=exclude_failures,
|
| + aliases=aliases)
|
| + deps = create_dependency_tree(ctx, quiet=quiet)
|
| + build_dir = getattr(options, 'build_dir', None)
|
| + modules_by_cfile = {}
|
| + to_compile = []
|
| + for m in module_list:
|
| + if build_dir:
|
| + root = os.path.realpath(os.path.abspath(find_root_package_dir(m.sources[0])))
|
| + def copy_to_build_dir(filepath, root=root):
|
| + filepath_abs = os.path.realpath(os.path.abspath(filepath))
|
| + if os.path.isabs(filepath):
|
| + filepath = filepath_abs
|
| + if filepath_abs.startswith(root):
|
| + mod_dir = os.path.join(build_dir,
|
| + os.path.dirname(_relpath(filepath, root)))
|
| + if not os.path.isdir(mod_dir):
|
| + os.makedirs(mod_dir)
|
| + shutil.copy(filepath, mod_dir)
|
| + for dep in m.depends:
|
| + copy_to_build_dir(dep)
|
| +
|
| + new_sources = []
|
| + for source in m.sources:
|
| + base, ext = os.path.splitext(source)
|
| + if ext in ('.pyx', '.py'):
|
| + if m.language == 'c++':
|
| + c_file = base + '.cpp'
|
| + options = cpp_options
|
| + else:
|
| + c_file = base + '.c'
|
| + options = c_options
|
| +
|
| + # setup for out of place build directory if enabled
|
| + if build_dir:
|
| + c_file = os.path.join(build_dir, c_file)
|
| + dir = os.path.dirname(c_file)
|
| + if not os.path.isdir(dir):
|
| + os.makedirs(dir)
|
| +
|
| + if os.path.exists(c_file):
|
| + c_timestamp = os.path.getmtime(c_file)
|
| + else:
|
| + c_timestamp = -1
|
| +
|
| + # Priority goes first to modified files, second to direct
|
| + # dependents, and finally to indirect dependents.
|
| + if c_timestamp < deps.timestamp(source):
|
| + dep_timestamp, dep = deps.timestamp(source), source
|
| + priority = 0
|
| + else:
|
| + dep_timestamp, dep = deps.newest_dependency(source)
|
| + priority = 2 - (dep in deps.immediate_dependencies(source))
|
| + if force or c_timestamp < dep_timestamp:
|
| + if not quiet:
|
| + if source == dep:
|
| + print("Compiling %s because it changed." % source)
|
| + else:
|
| + print("Compiling %s because it depends on %s." % (source, dep))
|
| + if not force and hasattr(options, 'cache'):
|
| + extra = m.language
|
| + fingerprint = deps.transitive_fingerprint(source, extra)
|
| + else:
|
| + fingerprint = None
|
| + to_compile.append((priority, source, c_file, fingerprint, quiet,
|
| + options, not exclude_failures))
|
| + new_sources.append(c_file)
|
| + if c_file not in modules_by_cfile:
|
| + modules_by_cfile[c_file] = [m]
|
| + else:
|
| + modules_by_cfile[c_file].append(m)
|
| + else:
|
| + new_sources.append(source)
|
| + if build_dir:
|
| + copy_to_build_dir(source)
|
| + m.sources = new_sources
|
| + if hasattr(options, 'cache'):
|
| + if not os.path.exists(options.cache):
|
| + os.makedirs(options.cache)
|
| + to_compile.sort()
|
| + if nthreads:
|
| + # Requires multiprocessing (or Python >= 2.6)
|
| + try:
|
| + import multiprocessing
|
| + pool = multiprocessing.Pool(nthreads)
|
| + except (ImportError, OSError):
|
| + print("multiprocessing required for parallel cythonization")
|
| + nthreads = 0
|
| + else:
|
| + pool.map(cythonize_one_helper, to_compile)
|
| + if not nthreads:
|
| + for args in to_compile:
|
| + cythonize_one(*args[1:])
|
| + if exclude_failures:
|
| + failed_modules = set()
|
| + for c_file, modules in modules_by_cfile.iteritems():
|
| + if not os.path.exists(c_file):
|
| + failed_modules.update(modules)
|
| + elif os.path.getsize(c_file) < 200:
|
| + f = io_open(c_file, 'r', encoding='iso8859-1')
|
| + try:
|
| + if f.read(len('#error ')) == '#error ':
|
| + # dead compilation result
|
| + failed_modules.update(modules)
|
| + finally:
|
| + f.close()
|
| + if failed_modules:
|
| + for module in failed_modules:
|
| + module_list.remove(module)
|
| + print("Failed compilations: %s" % ', '.join(sorted([
|
| + module.name for module in failed_modules])))
|
| + if hasattr(options, 'cache'):
|
| + cleanup_cache(options.cache, getattr(options, 'cache_size', 1024 * 1024 * 100))
|
| + # cythonize() is often followed by the (non-Python-buffered)
|
| + # compiler output, flush now to avoid interleaving output.
|
| + sys.stdout.flush()
|
| + return module_list
|
| +
|
| +
|
| +if os.environ.get('XML_RESULTS'):
|
| + compile_result_dir = os.environ['XML_RESULTS']
|
| + def record_results(func):
|
| + def with_record(*args):
|
| + t = time.time()
|
| + success = True
|
| + try:
|
| + try:
|
| + func(*args)
|
| + except:
|
| + success = False
|
| + finally:
|
| + t = time.time() - t
|
| + module = fully_qualified_name(args[0])
|
| + name = "cythonize." + module
|
| + failures = 1 - success
|
| + if success:
|
| + failure_item = ""
|
| + else:
|
| + failure_item = "failure"
|
| + output = open(os.path.join(compile_result_dir, name + ".xml"), "w")
|
| + output.write("""
|
| + <?xml version="1.0" ?>
|
| + <testsuite name="%(name)s" errors="0" failures="%(failures)s" tests="1" time="%(t)s">
|
| + <testcase classname="%(name)s" name="cythonize">
|
| + %(failure_item)s
|
| + </testcase>
|
| + </testsuite>
|
| + """.strip() % locals())
|
| + output.close()
|
| + return with_record
|
| +else:
|
| + record_results = lambda x: x
|
| +
|
| +# TODO: Share context? Issue: pyx processing leaks into pxd module
|
| +@record_results
|
| +def cythonize_one(pyx_file, c_file, fingerprint, quiet, options=None, raise_on_failure=True):
|
| + from Cython.Compiler.Main import compile, default_options
|
| + from Cython.Compiler.Errors import CompileError, PyrexError
|
| +
|
| + if fingerprint:
|
| + if not os.path.exists(options.cache):
|
| + try:
|
| + os.mkdir(options.cache)
|
| + except:
|
| + if not os.path.exists(options.cache):
|
| + raise
|
| + # Cython-generated c files are highly compressible.
|
| + # (E.g. a compression ratio of about 10 for Sage).
|
| + fingerprint_file = join_path(
|
| + options.cache, "%s-%s%s" % (os.path.basename(c_file), fingerprint, gzip_ext))
|
| + if os.path.exists(fingerprint_file):
|
| + if not quiet:
|
| + print("Found compiled %s in cache" % pyx_file)
|
| + os.utime(fingerprint_file, None)
|
| + g = gzip_open(fingerprint_file, 'rb')
|
| + try:
|
| + f = open(c_file, 'wb')
|
| + try:
|
| + shutil.copyfileobj(g, f)
|
| + finally:
|
| + f.close()
|
| + finally:
|
| + g.close()
|
| + return
|
| + if not quiet:
|
| + print("Cythonizing %s" % pyx_file)
|
| + if options is None:
|
| + options = CompilationOptions(default_options)
|
| + options.output_file = c_file
|
| +
|
| + any_failures = 0
|
| + try:
|
| + result = compile([pyx_file], options)
|
| + if result.num_errors > 0:
|
| + any_failures = 1
|
| + except (EnvironmentError, PyrexError), e:
|
| + sys.stderr.write('%s\n' % e)
|
| + any_failures = 1
|
| + # XXX
|
| + import traceback
|
| + traceback.print_exc()
|
| + except Exception:
|
| + if raise_on_failure:
|
| + raise
|
| + import traceback
|
| + traceback.print_exc()
|
| + any_failures = 1
|
| + if any_failures:
|
| + if raise_on_failure:
|
| + raise CompileError(None, pyx_file)
|
| + elif os.path.exists(c_file):
|
| + os.remove(c_file)
|
| + elif fingerprint:
|
| + f = open(c_file, 'rb')
|
| + try:
|
| + g = gzip_open(fingerprint_file, 'wb')
|
| + try:
|
| + shutil.copyfileobj(f, g)
|
| + finally:
|
| + g.close()
|
| + finally:
|
| + f.close()
|
| +
|
| +def cythonize_one_helper(m):
|
| + import traceback
|
| + try:
|
| + return cythonize_one(*m[1:])
|
| + except Exception:
|
| + traceback.print_exc()
|
| + raise
|
| +
|
| +def cleanup_cache(cache, target_size, ratio=.85):
|
| + try:
|
| + p = subprocess.Popen(['du', '-s', '-k', os.path.abspath(cache)], stdout=subprocess.PIPE)
|
| + res = p.wait()
|
| + if res == 0:
|
| + total_size = 1024 * int(p.stdout.read().strip().split()[0])
|
| + if total_size < target_size:
|
| + return
|
| + except (OSError, ValueError):
|
| + pass
|
| + total_size = 0
|
| + all = []
|
| + for file in os.listdir(cache):
|
| + path = join_path(cache, file)
|
| + s = os.stat(path)
|
| + total_size += s.st_size
|
| + all.append((s.st_atime, s.st_size, path))
|
| + if total_size > target_size:
|
| + for time, size, file in reversed(sorted(all)):
|
| + os.unlink(file)
|
| + total_size -= size
|
| + if total_size < target_size * ratio:
|
| + break
|
|
|