| import os |
| import re |
| import sys |
| import copy |
| import glob |
| import atexit |
| import tempfile |
| import subprocess |
| import shutil |
| import multiprocessing |
| import textwrap |
| import importlib.util |
| from threading import local as tlocal |
| from functools import reduce |
| |
| import distutils |
| from distutils.errors import DistutilsError |
| |
| # stores temporary directory of each thread to only create one per thread |
| _tdata = tlocal() |
| |
| # store all created temporary directories so they can be deleted on exit |
| _tmpdirs = [] |
| def clean_up_temporary_directory(): |
| if _tmpdirs is not None: |
| for d in _tmpdirs: |
| try: |
| shutil.rmtree(d) |
| except OSError: |
| pass |
| |
| atexit.register(clean_up_temporary_directory) |
| |
| __all__ = ['Configuration', 'get_numpy_include_dirs', 'default_config_dict', |
| 'dict_append', 'appendpath', 'generate_config_py', |
| 'get_cmd', 'allpath', 'get_mathlibs', |
| 'terminal_has_colors', 'red_text', 'green_text', 'yellow_text', |
| 'blue_text', 'cyan_text', 'cyg2win32', 'mingw32', 'all_strings', |
| 'has_f_sources', 'has_cxx_sources', 'filter_sources', |
| 'get_dependencies', 'is_local_src_dir', 'get_ext_source_files', |
| 'get_script_files', 'get_lib_source_files', 'get_data_files', |
| 'dot_join', 'get_frame', 'minrelpath', 'njoin', |
| 'is_sequence', 'is_string', 'as_list', 'gpaths', 'get_language', |
| 'get_build_architecture', 'get_info', 'get_pkg_info', |
| 'get_num_build_jobs', 'sanitize_cxx_flags', |
| 'exec_mod_from_location'] |
| |
| class InstallableLib: |
| """ |
| Container to hold information on an installable library. |
| |
| Parameters |
| ---------- |
| name : str |
| Name of the installed library. |
| build_info : dict |
| Dictionary holding build information. |
| target_dir : str |
| Absolute path specifying where to install the library. |
| |
| See Also |
| -------- |
| Configuration.add_installed_library |
| |
| Notes |
| ----- |
| The three parameters are stored as attributes with the same names. |
| |
| """ |
| def __init__(self, name, build_info, target_dir): |
| self.name = name |
| self.build_info = build_info |
| self.target_dir = target_dir |
| |
| |
| def get_num_build_jobs(): |
| """ |
| Get number of parallel build jobs set by the --parallel command line |
| argument of setup.py |
| If the command did not receive a setting the environment variable |
| NPY_NUM_BUILD_JOBS is checked. If that is unset, return the number of |
| processors on the system, with a maximum of 8 (to prevent |
| overloading the system if there a lot of CPUs). |
| |
| Returns |
| ------- |
| out : int |
| number of parallel jobs that can be run |
| |
| """ |
| from numpy.distutils.core import get_distribution |
| try: |
| cpu_count = len(os.sched_getaffinity(0)) |
| except AttributeError: |
| cpu_count = multiprocessing.cpu_count() |
| cpu_count = min(cpu_count, 8) |
| envjobs = int(os.environ.get("NPY_NUM_BUILD_JOBS", cpu_count)) |
| dist = get_distribution() |
| # may be None during configuration |
| if dist is None: |
| return envjobs |
| |
| # any of these three may have the job set, take the largest |
| cmdattr = (getattr(dist.get_command_obj('build'), 'parallel', None), |
| getattr(dist.get_command_obj('build_ext'), 'parallel', None), |
| getattr(dist.get_command_obj('build_clib'), 'parallel', None)) |
| if all(x is None for x in cmdattr): |
| return envjobs |
| else: |
| return max(x for x in cmdattr if x is not None) |
| |
| def quote_args(args): |
| """Quote list of arguments. |
| |
| .. deprecated:: 1.22. |
| """ |
| import warnings |
| warnings.warn('"quote_args" is deprecated.', |
| DeprecationWarning, stacklevel=2) |
| # don't used _nt_quote_args as it does not check if |
| # args items already have quotes or not. |
| args = list(args) |
| for i in range(len(args)): |
| a = args[i] |
| if ' ' in a and a[0] not in '"\'': |
| args[i] = '"%s"' % (a) |
| return args |
| |
| def allpath(name): |
| "Convert a /-separated pathname to one using the OS's path separator." |
| split = name.split('/') |
| return os.path.join(*split) |
| |
| def rel_path(path, parent_path): |
| """Return path relative to parent_path.""" |
| # Use realpath to avoid issues with symlinked dirs (see gh-7707) |
| pd = os.path.realpath(os.path.abspath(parent_path)) |
| apath = os.path.realpath(os.path.abspath(path)) |
| if len(apath) < len(pd): |
| return path |
| if apath == pd: |
| return '' |
| if pd == apath[:len(pd)]: |
| assert apath[len(pd)] in [os.sep], repr((path, apath[len(pd)])) |
| path = apath[len(pd)+1:] |
| return path |
| |
| def get_path_from_frame(frame, parent_path=None): |
| """Return path of the module given a frame object from the call stack. |
| |
| Returned path is relative to parent_path when given, |
| otherwise it is absolute path. |
| """ |
| |
| # First, try to find if the file name is in the frame. |
| try: |
| caller_file = eval('__file__', frame.f_globals, frame.f_locals) |
| d = os.path.dirname(os.path.abspath(caller_file)) |
| except NameError: |
| # __file__ is not defined, so let's try __name__. We try this second |
| # because setuptools spoofs __name__ to be '__main__' even though |
| # sys.modules['__main__'] might be something else, like easy_install(1). |
| caller_name = eval('__name__', frame.f_globals, frame.f_locals) |
| __import__(caller_name) |
| mod = sys.modules[caller_name] |
| if hasattr(mod, '__file__'): |
| d = os.path.dirname(os.path.abspath(mod.__file__)) |
| else: |
| # we're probably running setup.py as execfile("setup.py") |
| # (likely we're building an egg) |
| d = os.path.abspath('.') |
| |
| if parent_path is not None: |
| d = rel_path(d, parent_path) |
| |
| return d or '.' |
| |
| def njoin(*path): |
| """Join two or more pathname components + |
| - convert a /-separated pathname to one using the OS's path separator. |
| - resolve `..` and `.` from path. |
| |
| Either passing n arguments as in njoin('a','b'), or a sequence |
| of n names as in njoin(['a','b']) is handled, or a mixture of such arguments. |
| """ |
| paths = [] |
| for p in path: |
| if is_sequence(p): |
| # njoin(['a', 'b'], 'c') |
| paths.append(njoin(*p)) |
| else: |
| assert is_string(p) |
| paths.append(p) |
| path = paths |
| if not path: |
| # njoin() |
| joined = '' |
| else: |
| # njoin('a', 'b') |
| joined = os.path.join(*path) |
| if os.path.sep != '/': |
| joined = joined.replace('/', os.path.sep) |
| return minrelpath(joined) |
| |
| def get_mathlibs(path=None): |
| """Return the MATHLIB line from numpyconfig.h |
| """ |
| if path is not None: |
| config_file = os.path.join(path, '_numpyconfig.h') |
| else: |
| # Look for the file in each of the numpy include directories. |
| dirs = get_numpy_include_dirs() |
| for path in dirs: |
| fn = os.path.join(path, '_numpyconfig.h') |
| if os.path.exists(fn): |
| config_file = fn |
| break |
| else: |
| raise DistutilsError('_numpyconfig.h not found in numpy include ' |
| 'dirs %r' % (dirs,)) |
| |
| with open(config_file) as fid: |
| mathlibs = [] |
| s = '#define MATHLIB' |
| for line in fid: |
| if line.startswith(s): |
| value = line[len(s):].strip() |
| if value: |
| mathlibs.extend(value.split(',')) |
| return mathlibs |
| |
| def minrelpath(path): |
| """Resolve `..` and '.' from path. |
| """ |
| if not is_string(path): |
| return path |
| if '.' not in path: |
| return path |
| l = path.split(os.sep) |
| while l: |
| try: |
| i = l.index('.', 1) |
| except ValueError: |
| break |
| del l[i] |
| j = 1 |
| while l: |
| try: |
| i = l.index('..', j) |
| except ValueError: |
| break |
| if l[i-1]=='..': |
| j += 1 |
| else: |
| del l[i], l[i-1] |
| j = 1 |
| if not l: |
| return '' |
| return os.sep.join(l) |
| |
| def sorted_glob(fileglob): |
| """sorts output of python glob for https://bugs.python.org/issue30461 |
| to allow extensions to have reproducible build results""" |
| return sorted(glob.glob(fileglob)) |
| |
| def _fix_paths(paths, local_path, include_non_existing): |
| assert is_sequence(paths), repr(type(paths)) |
| new_paths = [] |
| assert not is_string(paths), repr(paths) |
| for n in paths: |
| if is_string(n): |
| if '*' in n or '?' in n: |
| p = sorted_glob(n) |
| p2 = sorted_glob(njoin(local_path, n)) |
| if p2: |
| new_paths.extend(p2) |
| elif p: |
| new_paths.extend(p) |
| else: |
| if include_non_existing: |
| new_paths.append(n) |
| print('could not resolve pattern in %r: %r' % |
| (local_path, n)) |
| else: |
| n2 = njoin(local_path, n) |
| if os.path.exists(n2): |
| new_paths.append(n2) |
| else: |
| if os.path.exists(n): |
| new_paths.append(n) |
| elif include_non_existing: |
| new_paths.append(n) |
| if not os.path.exists(n): |
| print('non-existing path in %r: %r' % |
| (local_path, n)) |
| |
| elif is_sequence(n): |
| new_paths.extend(_fix_paths(n, local_path, include_non_existing)) |
| else: |
| new_paths.append(n) |
| return [minrelpath(p) for p in new_paths] |
| |
| def gpaths(paths, local_path='', include_non_existing=True): |
| """Apply glob to paths and prepend local_path if needed. |
| """ |
| if is_string(paths): |
| paths = (paths,) |
| return _fix_paths(paths, local_path, include_non_existing) |
| |
| def make_temp_file(suffix='', prefix='', text=True): |
| if not hasattr(_tdata, 'tempdir'): |
| _tdata.tempdir = tempfile.mkdtemp() |
| _tmpdirs.append(_tdata.tempdir) |
| fid, name = tempfile.mkstemp(suffix=suffix, |
| prefix=prefix, |
| dir=_tdata.tempdir, |
| text=text) |
| fo = os.fdopen(fid, 'w') |
| return fo, name |
| |
| # Hooks for colored terminal output. |
| # See also https://web.archive.org/web/20100314204946/http://www.livinglogic.de/Python/ansistyle |
| def terminal_has_colors(): |
| if sys.platform=='cygwin' and 'USE_COLOR' not in os.environ: |
| # Avoid importing curses that causes illegal operation |
| # with a message: |
| # PYTHON2 caused an invalid page fault in |
| # module CYGNURSES7.DLL as 015f:18bbfc28 |
| # Details: Python 2.3.3 [GCC 3.3.1 (cygming special)] |
| # ssh to Win32 machine from debian |
| # curses.version is 2.2 |
| # CYGWIN_98-4.10, release 1.5.7(0.109/3/2)) |
| return 0 |
| if hasattr(sys.stdout, 'isatty') and sys.stdout.isatty(): |
| try: |
| import curses |
| curses.setupterm() |
| if (curses.tigetnum("colors") >= 0 |
| and curses.tigetnum("pairs") >= 0 |
| and ((curses.tigetstr("setf") is not None |
| and curses.tigetstr("setb") is not None) |
| or (curses.tigetstr("setaf") is not None |
| and curses.tigetstr("setab") is not None) |
| or curses.tigetstr("scp") is not None)): |
| return 1 |
| except Exception: |
| pass |
| return 0 |
| |
| if terminal_has_colors(): |
| _colour_codes = dict(black=0, red=1, green=2, yellow=3, |
| blue=4, magenta=5, cyan=6, white=7, default=9) |
| def colour_text(s, fg=None, bg=None, bold=False): |
| seq = [] |
| if bold: |
| seq.append('1') |
| if fg: |
| fgcode = 30 + _colour_codes.get(fg.lower(), 0) |
| seq.append(str(fgcode)) |
| if bg: |
| bgcode = 40 + _colour_codes.get(bg.lower(), 7) |
| seq.append(str(bgcode)) |
| if seq: |
| return '\x1b[%sm%s\x1b[0m' % (';'.join(seq), s) |
| else: |
| return s |
| else: |
| def colour_text(s, fg=None, bg=None): |
| return s |
| |
| def default_text(s): |
| return colour_text(s, 'default') |
| def red_text(s): |
| return colour_text(s, 'red') |
| def green_text(s): |
| return colour_text(s, 'green') |
| def yellow_text(s): |
| return colour_text(s, 'yellow') |
| def cyan_text(s): |
| return colour_text(s, 'cyan') |
| def blue_text(s): |
| return colour_text(s, 'blue') |
| |
| ######################### |
| |
| def cyg2win32(path: str) -> str: |
| """Convert a path from Cygwin-native to Windows-native. |
| |
| Uses the cygpath utility (part of the Base install) to do the |
| actual conversion. Falls back to returning the original path if |
| this fails. |
| |
| Handles the default ``/cygdrive`` mount prefix as well as the |
| ``/proc/cygdrive`` portable prefix, custom cygdrive prefixes such |
| as ``/`` or ``/mnt``, and absolute paths such as ``/usr/src/`` or |
| ``/home/username`` |
| |
| Parameters |
| ---------- |
| path : str |
| The path to convert |
| |
| Returns |
| ------- |
| converted_path : str |
| The converted path |
| |
| Notes |
| ----- |
| Documentation for cygpath utility: |
| https://cygwin.com/cygwin-ug-net/cygpath.html |
| Documentation for the C function it wraps: |
| https://cygwin.com/cygwin-api/func-cygwin-conv-path.html |
| |
| """ |
| if sys.platform != "cygwin": |
| return path |
| return subprocess.check_output( |
| ["/usr/bin/cygpath", "--windows", path], text=True |
| ) |
| |
| |
| def mingw32(): |
| """Return true when using mingw32 environment. |
| """ |
| if sys.platform=='win32': |
| if os.environ.get('OSTYPE', '')=='msys': |
| return True |
| if os.environ.get('MSYSTEM', '')=='MINGW32': |
| return True |
| return False |
| |
| def msvc_runtime_version(): |
| "Return version of MSVC runtime library, as defined by __MSC_VER__ macro" |
| msc_pos = sys.version.find('MSC v.') |
| if msc_pos != -1: |
| msc_ver = int(sys.version[msc_pos+6:msc_pos+10]) |
| else: |
| msc_ver = None |
| return msc_ver |
| |
| def msvc_runtime_library(): |
| "Return name of MSVC runtime library if Python was built with MSVC >= 7" |
| ver = msvc_runtime_major () |
| if ver: |
| if ver < 140: |
| return "msvcr%i" % ver |
| else: |
| return "vcruntime%i" % ver |
| else: |
| return None |
| |
| def msvc_runtime_major(): |
| "Return major version of MSVC runtime coded like get_build_msvc_version" |
| major = {1300: 70, # MSVC 7.0 |
| 1310: 71, # MSVC 7.1 |
| 1400: 80, # MSVC 8 |
| 1500: 90, # MSVC 9 (aka 2008) |
| 1600: 100, # MSVC 10 (aka 2010) |
| 1900: 140, # MSVC 14 (aka 2015) |
| }.get(msvc_runtime_version(), None) |
| return major |
| |
| ######################### |
| |
| #XXX need support for .C that is also C++ |
| cxx_ext_match = re.compile(r'.*\.(cpp|cxx|cc)\Z', re.I).match |
| fortran_ext_match = re.compile(r'.*\.(f90|f95|f77|for|ftn|f)\Z', re.I).match |
| f90_ext_match = re.compile(r'.*\.(f90|f95)\Z', re.I).match |
| f90_module_name_match = re.compile(r'\s*module\s*(?P<name>[\w_]+)', re.I).match |
| def _get_f90_modules(source): |
| """Return a list of Fortran f90 module names that |
| given source file defines. |
| """ |
| if not f90_ext_match(source): |
| return [] |
| modules = [] |
| with open(source) as f: |
| for line in f: |
| m = f90_module_name_match(line) |
| if m: |
| name = m.group('name') |
| modules.append(name) |
| # break # XXX can we assume that there is one module per file? |
| return modules |
| |
| def is_string(s): |
| return isinstance(s, str) |
| |
| def all_strings(lst): |
| """Return True if all items in lst are string objects. """ |
| for item in lst: |
| if not is_string(item): |
| return False |
| return True |
| |
| def is_sequence(seq): |
| if is_string(seq): |
| return False |
| try: |
| len(seq) |
| except Exception: |
| return False |
| return True |
| |
| def is_glob_pattern(s): |
| return is_string(s) and ('*' in s or '?' in s) |
| |
| def as_list(seq): |
| if is_sequence(seq): |
| return list(seq) |
| else: |
| return [seq] |
| |
| def get_language(sources): |
| # not used in numpy/scipy packages, use build_ext.detect_language instead |
| """Determine language value (c,f77,f90) from sources """ |
| language = None |
| for source in sources: |
| if isinstance(source, str): |
| if f90_ext_match(source): |
| language = 'f90' |
| break |
| elif fortran_ext_match(source): |
| language = 'f77' |
| return language |
| |
| def has_f_sources(sources): |
| """Return True if sources contains Fortran files """ |
| for source in sources: |
| if fortran_ext_match(source): |
| return True |
| return False |
| |
| def has_cxx_sources(sources): |
| """Return True if sources contains C++ files """ |
| for source in sources: |
| if cxx_ext_match(source): |
| return True |
| return False |
| |
| def filter_sources(sources): |
| """Return four lists of filenames containing |
| C, C++, Fortran, and Fortran 90 module sources, |
| respectively. |
| """ |
| c_sources = [] |
| cxx_sources = [] |
| f_sources = [] |
| fmodule_sources = [] |
| for source in sources: |
| if fortran_ext_match(source): |
| modules = _get_f90_modules(source) |
| if modules: |
| fmodule_sources.append(source) |
| else: |
| f_sources.append(source) |
| elif cxx_ext_match(source): |
| cxx_sources.append(source) |
| else: |
| c_sources.append(source) |
| return c_sources, cxx_sources, f_sources, fmodule_sources |
| |
| |
| def _get_headers(directory_list): |
| # get *.h files from list of directories |
| headers = [] |
| for d in directory_list: |
| head = sorted_glob(os.path.join(d, "*.h")) #XXX: *.hpp files?? |
| headers.extend(head) |
| return headers |
| |
| def _get_directories(list_of_sources): |
| # get unique directories from list of sources. |
| direcs = [] |
| for f in list_of_sources: |
| d = os.path.split(f) |
| if d[0] != '' and not d[0] in direcs: |
| direcs.append(d[0]) |
| return direcs |
| |
| def _commandline_dep_string(cc_args, extra_postargs, pp_opts): |
| """ |
| Return commandline representation used to determine if a file needs |
| to be recompiled |
| """ |
| cmdline = 'commandline: ' |
| cmdline += ' '.join(cc_args) |
| cmdline += ' '.join(extra_postargs) |
| cmdline += ' '.join(pp_opts) + '\n' |
| return cmdline |
| |
| |
| def get_dependencies(sources): |
| #XXX scan sources for include statements |
| return _get_headers(_get_directories(sources)) |
| |
| def is_local_src_dir(directory): |
| """Return true if directory is local directory. |
| """ |
| if not is_string(directory): |
| return False |
| abs_dir = os.path.abspath(directory) |
| c = os.path.commonprefix([os.getcwd(), abs_dir]) |
| new_dir = abs_dir[len(c):].split(os.sep) |
| if new_dir and not new_dir[0]: |
| new_dir = new_dir[1:] |
| if new_dir and new_dir[0]=='build': |
| return False |
| new_dir = os.sep.join(new_dir) |
| return os.path.isdir(new_dir) |
| |
| def general_source_files(top_path): |
| pruned_directories = {'CVS':1, '.svn':1, 'build':1} |
| prune_file_pat = re.compile(r'(?:[~#]|\.py[co]|\.o)$') |
| for dirpath, dirnames, filenames in os.walk(top_path, topdown=True): |
| pruned = [ d for d in dirnames if d not in pruned_directories ] |
| dirnames[:] = pruned |
| for f in filenames: |
| if not prune_file_pat.search(f): |
| yield os.path.join(dirpath, f) |
| |
| def general_source_directories_files(top_path): |
| """Return a directory name relative to top_path and |
| files contained. |
| """ |
| pruned_directories = ['CVS', '.svn', 'build'] |
| prune_file_pat = re.compile(r'(?:[~#]|\.py[co]|\.o)$') |
| for dirpath, dirnames, filenames in os.walk(top_path, topdown=True): |
| pruned = [ d for d in dirnames if d not in pruned_directories ] |
| dirnames[:] = pruned |
| for d in dirnames: |
| dpath = os.path.join(dirpath, d) |
| rpath = rel_path(dpath, top_path) |
| files = [] |
| for f in os.listdir(dpath): |
| fn = os.path.join(dpath, f) |
| if os.path.isfile(fn) and not prune_file_pat.search(fn): |
| files.append(fn) |
| yield rpath, files |
| dpath = top_path |
| rpath = rel_path(dpath, top_path) |
| filenames = [os.path.join(dpath, f) for f in os.listdir(dpath) \ |
| if not prune_file_pat.search(f)] |
| files = [f for f in filenames if os.path.isfile(f)] |
| yield rpath, files |
| |
| |
| def get_ext_source_files(ext): |
| # Get sources and any include files in the same directory. |
| filenames = [] |
| sources = [_m for _m in ext.sources if is_string(_m)] |
| filenames.extend(sources) |
| filenames.extend(get_dependencies(sources)) |
| for d in ext.depends: |
| if is_local_src_dir(d): |
| filenames.extend(list(general_source_files(d))) |
| elif os.path.isfile(d): |
| filenames.append(d) |
| return filenames |
| |
| def get_script_files(scripts): |
| scripts = [_m for _m in scripts if is_string(_m)] |
| return scripts |
| |
| def get_lib_source_files(lib): |
| filenames = [] |
| sources = lib[1].get('sources', []) |
| sources = [_m for _m in sources if is_string(_m)] |
| filenames.extend(sources) |
| filenames.extend(get_dependencies(sources)) |
| depends = lib[1].get('depends', []) |
| for d in depends: |
| if is_local_src_dir(d): |
| filenames.extend(list(general_source_files(d))) |
| elif os.path.isfile(d): |
| filenames.append(d) |
| return filenames |
| |
| def get_shared_lib_extension(is_python_ext=False): |
| """Return the correct file extension for shared libraries. |
| |
| Parameters |
| ---------- |
| is_python_ext : bool, optional |
| Whether the shared library is a Python extension. Default is False. |
| |
| Returns |
| ------- |
| so_ext : str |
| The shared library extension. |
| |
| Notes |
| ----- |
| For Python shared libs, `so_ext` will typically be '.so' on Linux and OS X, |
| and '.pyd' on Windows. For Python >= 3.2 `so_ext` has a tag prepended on |
| POSIX systems according to PEP 3149. |
| |
| """ |
| confvars = distutils.sysconfig.get_config_vars() |
| so_ext = confvars.get('EXT_SUFFIX', '') |
| |
| if not is_python_ext: |
| # hardcode known values, config vars (including SHLIB_SUFFIX) are |
| # unreliable (see #3182) |
| # darwin, windows and debug linux are wrong in 3.3.1 and older |
| if (sys.platform.startswith('linux') or |
| sys.platform.startswith('gnukfreebsd')): |
| so_ext = '.so' |
| elif sys.platform.startswith('darwin'): |
| so_ext = '.dylib' |
| elif sys.platform.startswith('win'): |
| so_ext = '.dll' |
| else: |
| # fall back to config vars for unknown platforms |
| # fix long extension for Python >=3.2, see PEP 3149. |
| if 'SOABI' in confvars: |
| # Does nothing unless SOABI config var exists |
| so_ext = so_ext.replace('.' + confvars.get('SOABI'), '', 1) |
| |
| return so_ext |
| |
| def get_data_files(data): |
| if is_string(data): |
| return [data] |
| sources = data[1] |
| filenames = [] |
| for s in sources: |
| if hasattr(s, '__call__'): |
| continue |
| if is_local_src_dir(s): |
| filenames.extend(list(general_source_files(s))) |
| elif is_string(s): |
| if os.path.isfile(s): |
| filenames.append(s) |
| else: |
| print('Not existing data file:', s) |
| else: |
| raise TypeError(repr(s)) |
| return filenames |
| |
| def dot_join(*args): |
| return '.'.join([a for a in args if a]) |
| |
| def get_frame(level=0): |
| """Return frame object from call stack with given level. |
| """ |
| try: |
| return sys._getframe(level+1) |
| except AttributeError: |
| frame = sys.exc_info()[2].tb_frame |
| for _ in range(level+1): |
| frame = frame.f_back |
| return frame |
| |
| |
| ###################### |
| |
| class Configuration: |
| |
| _list_keys = ['packages', 'ext_modules', 'data_files', 'include_dirs', |
| 'libraries', 'headers', 'scripts', 'py_modules', |
| 'installed_libraries', 'define_macros'] |
| _dict_keys = ['package_dir', 'installed_pkg_config'] |
| _extra_keys = ['name', 'version'] |
| |
| numpy_include_dirs = [] |
| |
| def __init__(self, |
| package_name=None, |
| parent_name=None, |
| top_path=None, |
| package_path=None, |
| caller_level=1, |
| setup_name='setup.py', |
| **attrs): |
| """Construct configuration instance of a package. |
| |
| package_name -- name of the package |
| Ex.: 'distutils' |
| parent_name -- name of the parent package |
| Ex.: 'numpy' |
| top_path -- directory of the toplevel package |
| Ex.: the directory where the numpy package source sits |
| package_path -- directory of package. Will be computed by magic from the |
| directory of the caller module if not specified |
| Ex.: the directory where numpy.distutils is |
| caller_level -- frame level to caller namespace, internal parameter. |
| """ |
| self.name = dot_join(parent_name, package_name) |
| self.version = None |
| |
| caller_frame = get_frame(caller_level) |
| self.local_path = get_path_from_frame(caller_frame, top_path) |
| # local_path -- directory of a file (usually setup.py) that |
| # defines a configuration() function. |
| # local_path -- directory of a file (usually setup.py) that |
| # defines a configuration() function. |
| if top_path is None: |
| top_path = self.local_path |
| self.local_path = '' |
| if package_path is None: |
| package_path = self.local_path |
| elif os.path.isdir(njoin(self.local_path, package_path)): |
| package_path = njoin(self.local_path, package_path) |
| if not os.path.isdir(package_path or '.'): |
| raise ValueError("%r is not a directory" % (package_path,)) |
| self.top_path = top_path |
| self.package_path = package_path |
| # this is the relative path in the installed package |
| self.path_in_package = os.path.join(*self.name.split('.')) |
| |
| self.list_keys = self._list_keys[:] |
| self.dict_keys = self._dict_keys[:] |
| |
| for n in self.list_keys: |
| v = copy.copy(attrs.get(n, [])) |
| setattr(self, n, as_list(v)) |
| |
| for n in self.dict_keys: |
| v = copy.copy(attrs.get(n, {})) |
| setattr(self, n, v) |
| |
| known_keys = self.list_keys + self.dict_keys |
| self.extra_keys = self._extra_keys[:] |
| for n in attrs.keys(): |
| if n in known_keys: |
| continue |
| a = attrs[n] |
| setattr(self, n, a) |
| if isinstance(a, list): |
| self.list_keys.append(n) |
| elif isinstance(a, dict): |
| self.dict_keys.append(n) |
| else: |
| self.extra_keys.append(n) |
| |
| if os.path.exists(njoin(package_path, '__init__.py')): |
| self.packages.append(self.name) |
| self.package_dir[self.name] = package_path |
| |
| self.options = dict( |
| ignore_setup_xxx_py = False, |
| assume_default_configuration = False, |
| delegate_options_to_subpackages = False, |
| quiet = False, |
| ) |
| |
| caller_instance = None |
| for i in range(1, 3): |
| try: |
| f = get_frame(i) |
| except ValueError: |
| break |
| try: |
| caller_instance = eval('self', f.f_globals, f.f_locals) |
| break |
| except NameError: |
| pass |
| if isinstance(caller_instance, self.__class__): |
| if caller_instance.options['delegate_options_to_subpackages']: |
| self.set_options(**caller_instance.options) |
| |
| self.setup_name = setup_name |
| |
| def todict(self): |
| """ |
| Return a dictionary compatible with the keyword arguments of distutils |
| setup function. |
| |
| Examples |
| -------- |
| >>> setup(**config.todict()) #doctest: +SKIP |
| """ |
| |
| self._optimize_data_files() |
| d = {} |
| known_keys = self.list_keys + self.dict_keys + self.extra_keys |
| for n in known_keys: |
| a = getattr(self, n) |
| if a: |
| d[n] = a |
| return d |
| |
| def info(self, message): |
| if not self.options['quiet']: |
| print(message) |
| |
| def warn(self, message): |
| sys.stderr.write('Warning: %s\n' % (message,)) |
| |
| def set_options(self, **options): |
| """ |
| Configure Configuration instance. |
| |
| The following options are available: |
| - ignore_setup_xxx_py |
| - assume_default_configuration |
| - delegate_options_to_subpackages |
| - quiet |
| |
| """ |
| for key, value in options.items(): |
| if key in self.options: |
| self.options[key] = value |
| else: |
| raise ValueError('Unknown option: '+key) |
| |
| def get_distribution(self): |
| """Return the distutils distribution object for self.""" |
| from numpy.distutils.core import get_distribution |
| return get_distribution() |
| |
| def _wildcard_get_subpackage(self, subpackage_name, |
| parent_name, |
| caller_level = 1): |
| l = subpackage_name.split('.') |
| subpackage_path = njoin([self.local_path]+l) |
| dirs = [_m for _m in sorted_glob(subpackage_path) if os.path.isdir(_m)] |
| config_list = [] |
| for d in dirs: |
| if not os.path.isfile(njoin(d, '__init__.py')): |
| continue |
| if 'build' in d.split(os.sep): |
| continue |
| n = '.'.join(d.split(os.sep)[-len(l):]) |
| c = self.get_subpackage(n, |
| parent_name = parent_name, |
| caller_level = caller_level+1) |
| config_list.extend(c) |
| return config_list |
| |
| def _get_configuration_from_setup_py(self, setup_py, |
| subpackage_name, |
| subpackage_path, |
| parent_name, |
| caller_level = 1): |
| # In case setup_py imports local modules: |
| sys.path.insert(0, os.path.dirname(setup_py)) |
| try: |
| setup_name = os.path.splitext(os.path.basename(setup_py))[0] |
| n = dot_join(self.name, subpackage_name, setup_name) |
| setup_module = exec_mod_from_location( |
| '_'.join(n.split('.')), setup_py) |
| if not hasattr(setup_module, 'configuration'): |
| if not self.options['assume_default_configuration']: |
| self.warn('Assuming default configuration '\ |
| '(%s does not define configuration())'\ |
| % (setup_module)) |
| config = Configuration(subpackage_name, parent_name, |
| self.top_path, subpackage_path, |
| caller_level = caller_level + 1) |
| else: |
| pn = dot_join(*([parent_name] + subpackage_name.split('.')[:-1])) |
| args = (pn,) |
| if setup_module.configuration.__code__.co_argcount > 1: |
| args = args + (self.top_path,) |
| config = setup_module.configuration(*args) |
| if config.name!=dot_join(parent_name, subpackage_name): |
| self.warn('Subpackage %r configuration returned as %r' % \ |
| (dot_join(parent_name, subpackage_name), config.name)) |
| finally: |
| del sys.path[0] |
| return config |
| |
| def get_subpackage(self,subpackage_name, |
| subpackage_path=None, |
| parent_name=None, |
| caller_level = 1): |
| """Return list of subpackage configurations. |
| |
| Parameters |
| ---------- |
| subpackage_name : str or None |
| Name of the subpackage to get the configuration. '*' in |
| subpackage_name is handled as a wildcard. |
| subpackage_path : str |
| If None, then the path is assumed to be the local path plus the |
| subpackage_name. If a setup.py file is not found in the |
| subpackage_path, then a default configuration is used. |
| parent_name : str |
| Parent name. |
| """ |
| if subpackage_name is None: |
| if subpackage_path is None: |
| raise ValueError( |
| "either subpackage_name or subpackage_path must be specified") |
| subpackage_name = os.path.basename(subpackage_path) |
| |
| # handle wildcards |
| l = subpackage_name.split('.') |
| if subpackage_path is None and '*' in subpackage_name: |
| return self._wildcard_get_subpackage(subpackage_name, |
| parent_name, |
| caller_level = caller_level+1) |
| assert '*' not in subpackage_name, repr((subpackage_name, subpackage_path, parent_name)) |
| if subpackage_path is None: |
| subpackage_path = njoin([self.local_path] + l) |
| else: |
| subpackage_path = njoin([subpackage_path] + l[:-1]) |
| subpackage_path = self.paths([subpackage_path])[0] |
| setup_py = njoin(subpackage_path, self.setup_name) |
| if not self.options['ignore_setup_xxx_py']: |
| if not os.path.isfile(setup_py): |
| setup_py = njoin(subpackage_path, |
| 'setup_%s.py' % (subpackage_name)) |
| if not os.path.isfile(setup_py): |
| if not self.options['assume_default_configuration']: |
| self.warn('Assuming default configuration '\ |
| '(%s/{setup_%s,setup}.py was not found)' \ |
| % (os.path.dirname(setup_py), subpackage_name)) |
| config = Configuration(subpackage_name, parent_name, |
| self.top_path, subpackage_path, |
| caller_level = caller_level+1) |
| else: |
| config = self._get_configuration_from_setup_py( |
| setup_py, |
| subpackage_name, |
| subpackage_path, |
| parent_name, |
| caller_level = caller_level + 1) |
| if config: |
| return [config] |
| else: |
| return [] |
| |
| def add_subpackage(self,subpackage_name, |
| subpackage_path=None, |
| standalone = False): |
| """Add a sub-package to the current Configuration instance. |
| |
| This is useful in a setup.py script for adding sub-packages to a |
| package. |
| |
| Parameters |
| ---------- |
| subpackage_name : str |
| name of the subpackage |
| subpackage_path : str |
| if given, the subpackage path such as the subpackage is in |
| subpackage_path / subpackage_name. If None,the subpackage is |
| assumed to be located in the local path / subpackage_name. |
| standalone : bool |
| """ |
| |
| if standalone: |
| parent_name = None |
| else: |
| parent_name = self.name |
| config_list = self.get_subpackage(subpackage_name, subpackage_path, |
| parent_name = parent_name, |
| caller_level = 2) |
| if not config_list: |
| self.warn('No configuration returned, assuming unavailable.') |
| for config in config_list: |
| d = config |
| if isinstance(config, Configuration): |
| d = config.todict() |
| assert isinstance(d, dict), repr(type(d)) |
| |
| self.info('Appending %s configuration to %s' \ |
| % (d.get('name'), self.name)) |
| self.dict_append(**d) |
| |
| dist = self.get_distribution() |
| if dist is not None: |
| self.warn('distutils distribution has been initialized,'\ |
| ' it may be too late to add a subpackage '+ subpackage_name) |
| |
| def add_data_dir(self, data_path): |
| """Recursively add files under data_path to data_files list. |
| |
| Recursively add files under data_path to the list of data_files to be |
| installed (and distributed). The data_path can be either a relative |
| path-name, or an absolute path-name, or a 2-tuple where the first |
| argument shows where in the install directory the data directory |
| should be installed to. |
| |
| Parameters |
| ---------- |
| data_path : seq or str |
| Argument can be either |
| |
| * 2-sequence (<datadir suffix>, <path to data directory>) |
| * path to data directory where python datadir suffix defaults |
| to package dir. |
| |
| Notes |
| ----- |
| Rules for installation paths:: |
| |
| foo/bar -> (foo/bar, foo/bar) -> parent/foo/bar |
| (gun, foo/bar) -> parent/gun |
| foo/* -> (foo/a, foo/a), (foo/b, foo/b) -> parent/foo/a, parent/foo/b |
| (gun, foo/*) -> (gun, foo/a), (gun, foo/b) -> gun |
| (gun/*, foo/*) -> parent/gun/a, parent/gun/b |
| /foo/bar -> (bar, /foo/bar) -> parent/bar |
| (gun, /foo/bar) -> parent/gun |
| (fun/*/gun/*, sun/foo/bar) -> parent/fun/foo/gun/bar |
| |
| Examples |
| -------- |
| For example suppose the source directory contains fun/foo.dat and |
| fun/bar/car.dat: |
| |
| >>> self.add_data_dir('fun') #doctest: +SKIP |
| >>> self.add_data_dir(('sun', 'fun')) #doctest: +SKIP |
| >>> self.add_data_dir(('gun', '/full/path/to/fun'))#doctest: +SKIP |
| |
| Will install data-files to the locations:: |
| |
| <package install directory>/ |
| fun/ |
| foo.dat |
| bar/ |
| car.dat |
| sun/ |
| foo.dat |
| bar/ |
| car.dat |
| gun/ |
| foo.dat |
| car.dat |
| |
| """ |
| if is_sequence(data_path): |
| d, data_path = data_path |
| else: |
| d = None |
| if is_sequence(data_path): |
| [self.add_data_dir((d, p)) for p in data_path] |
| return |
| if not is_string(data_path): |
| raise TypeError("not a string: %r" % (data_path,)) |
| if d is None: |
| if os.path.isabs(data_path): |
| return self.add_data_dir((os.path.basename(data_path), data_path)) |
| return self.add_data_dir((data_path, data_path)) |
| paths = self.paths(data_path, include_non_existing=False) |
| if is_glob_pattern(data_path): |
| if is_glob_pattern(d): |
| pattern_list = allpath(d).split(os.sep) |
| pattern_list.reverse() |
| # /a/*//b/ -> /a/*/b |
| rl = list(range(len(pattern_list)-1)); rl.reverse() |
| for i in rl: |
| if not pattern_list[i]: |
| del pattern_list[i] |
| # |
| for path in paths: |
| if not os.path.isdir(path): |
| print('Not a directory, skipping', path) |
| continue |
| rpath = rel_path(path, self.local_path) |
| path_list = rpath.split(os.sep) |
| path_list.reverse() |
| target_list = [] |
| i = 0 |
| for s in pattern_list: |
| if is_glob_pattern(s): |
| if i>=len(path_list): |
| raise ValueError('cannot fill pattern %r with %r' \ |
| % (d, path)) |
| target_list.append(path_list[i]) |
| else: |
| assert s==path_list[i], repr((s, path_list[i], data_path, d, path, rpath)) |
| target_list.append(s) |
| i += 1 |
| if path_list[i:]: |
| self.warn('mismatch of pattern_list=%s and path_list=%s'\ |
| % (pattern_list, path_list)) |
| target_list.reverse() |
| self.add_data_dir((os.sep.join(target_list), path)) |
| else: |
| for path in paths: |
| self.add_data_dir((d, path)) |
| return |
| assert not is_glob_pattern(d), repr(d) |
| |
| dist = self.get_distribution() |
| if dist is not None and dist.data_files is not None: |
| data_files = dist.data_files |
| else: |
| data_files = self.data_files |
| |
| for path in paths: |
| for d1, f in list(general_source_directories_files(path)): |
| target_path = os.path.join(self.path_in_package, d, d1) |
| data_files.append((target_path, f)) |
| |
| def _optimize_data_files(self): |
| data_dict = {} |
| for p, files in self.data_files: |
| if p not in data_dict: |
| data_dict[p] = set() |
| for f in files: |
| data_dict[p].add(f) |
| self.data_files[:] = [(p, list(files)) for p, files in data_dict.items()] |
| |
| def add_data_files(self,*files): |
| """Add data files to configuration data_files. |
| |
| Parameters |
| ---------- |
| files : sequence |
| Argument(s) can be either |
| |
| * 2-sequence (<datadir prefix>,<path to data file(s)>) |
| * paths to data files where python datadir prefix defaults |
| to package dir. |
| |
| Notes |
| ----- |
| The form of each element of the files sequence is very flexible |
| allowing many combinations of where to get the files from the package |
| and where they should ultimately be installed on the system. The most |
| basic usage is for an element of the files argument sequence to be a |
| simple filename. This will cause that file from the local path to be |
| installed to the installation path of the self.name package (package |
| path). The file argument can also be a relative path in which case the |
| entire relative path will be installed into the package directory. |
| Finally, the file can be an absolute path name in which case the file |
| will be found at the absolute path name but installed to the package |
| path. |
| |
| This basic behavior can be augmented by passing a 2-tuple in as the |
| file argument. The first element of the tuple should specify the |
| relative path (under the package install directory) where the |
| remaining sequence of files should be installed to (it has nothing to |
| do with the file-names in the source distribution). The second element |
| of the tuple is the sequence of files that should be installed. The |
| files in this sequence can be filenames, relative paths, or absolute |
| paths. For absolute paths the file will be installed in the top-level |
| package installation directory (regardless of the first argument). |
| Filenames and relative path names will be installed in the package |
| install directory under the path name given as the first element of |
| the tuple. |
| |
| Rules for installation paths: |
| |
| #. file.txt -> (., file.txt)-> parent/file.txt |
| #. foo/file.txt -> (foo, foo/file.txt) -> parent/foo/file.txt |
| #. /foo/bar/file.txt -> (., /foo/bar/file.txt) -> parent/file.txt |
| #. ``*``.txt -> parent/a.txt, parent/b.txt |
| #. foo/``*``.txt`` -> parent/foo/a.txt, parent/foo/b.txt |
| #. ``*/*.txt`` -> (``*``, ``*``/``*``.txt) -> parent/c/a.txt, parent/d/b.txt |
| #. (sun, file.txt) -> parent/sun/file.txt |
| #. (sun, bar/file.txt) -> parent/sun/file.txt |
| #. (sun, /foo/bar/file.txt) -> parent/sun/file.txt |
| #. (sun, ``*``.txt) -> parent/sun/a.txt, parent/sun/b.txt |
| #. (sun, bar/``*``.txt) -> parent/sun/a.txt, parent/sun/b.txt |
| #. (sun/``*``, ``*``/``*``.txt) -> parent/sun/c/a.txt, parent/d/b.txt |
| |
| An additional feature is that the path to a data-file can actually be |
| a function that takes no arguments and returns the actual path(s) to |
| the data-files. This is useful when the data files are generated while |
| building the package. |
| |
| Examples |
| -------- |
| Add files to the list of data_files to be included with the package. |
| |
| >>> self.add_data_files('foo.dat', |
| ... ('fun', ['gun.dat', 'nun/pun.dat', '/tmp/sun.dat']), |
| ... 'bar/cat.dat', |
| ... '/full/path/to/can.dat') #doctest: +SKIP |
| |
| will install these data files to:: |
| |
| <package install directory>/ |
| foo.dat |
| fun/ |
| gun.dat |
| nun/ |
| pun.dat |
| sun.dat |
| bar/ |
| car.dat |
| can.dat |
| |
| where <package install directory> is the package (or sub-package) |
| directory such as '/usr/lib/python2.4/site-packages/mypackage' ('C: |
| \\Python2.4 \\Lib \\site-packages \\mypackage') or |
| '/usr/lib/python2.4/site- packages/mypackage/mysubpackage' ('C: |
| \\Python2.4 \\Lib \\site-packages \\mypackage \\mysubpackage'). |
| """ |
| |
| if len(files)>1: |
| for f in files: |
| self.add_data_files(f) |
| return |
| assert len(files)==1 |
| if is_sequence(files[0]): |
| d, files = files[0] |
| else: |
| d = None |
| if is_string(files): |
| filepat = files |
| elif is_sequence(files): |
| if len(files)==1: |
| filepat = files[0] |
| else: |
| for f in files: |
| self.add_data_files((d, f)) |
| return |
| else: |
| raise TypeError(repr(type(files))) |
| |
| if d is None: |
| if hasattr(filepat, '__call__'): |
| d = '' |
| elif os.path.isabs(filepat): |
| d = '' |
| else: |
| d = os.path.dirname(filepat) |
| self.add_data_files((d, files)) |
| return |
| |
| paths = self.paths(filepat, include_non_existing=False) |
| if is_glob_pattern(filepat): |
| if is_glob_pattern(d): |
| pattern_list = d.split(os.sep) |
| pattern_list.reverse() |
| for path in paths: |
| path_list = path.split(os.sep) |
| path_list.reverse() |
| path_list.pop() # filename |
| target_list = [] |
| i = 0 |
| for s in pattern_list: |
| if is_glob_pattern(s): |
| target_list.append(path_list[i]) |
| i += 1 |
| else: |
| target_list.append(s) |
| target_list.reverse() |
| self.add_data_files((os.sep.join(target_list), path)) |
| else: |
| self.add_data_files((d, paths)) |
| return |
| assert not is_glob_pattern(d), repr((d, filepat)) |
| |
| dist = self.get_distribution() |
| if dist is not None and dist.data_files is not None: |
| data_files = dist.data_files |
| else: |
| data_files = self.data_files |
| |
| data_files.append((os.path.join(self.path_in_package, d), paths)) |
| |
| ### XXX Implement add_py_modules |
| |
| def add_define_macros(self, macros): |
| """Add define macros to configuration |
| |
| Add the given sequence of macro name and value duples to the beginning |
| of the define_macros list This list will be visible to all extension |
| modules of the current package. |
| """ |
| dist = self.get_distribution() |
| if dist is not None: |
| if not hasattr(dist, 'define_macros'): |
| dist.define_macros = [] |
| dist.define_macros.extend(macros) |
| else: |
| self.define_macros.extend(macros) |
| |
| |
| def add_include_dirs(self,*paths): |
| """Add paths to configuration include directories. |
| |
| Add the given sequence of paths to the beginning of the include_dirs |
| list. This list will be visible to all extension modules of the |
| current package. |
| """ |
| include_dirs = self.paths(paths) |
| dist = self.get_distribution() |
| if dist is not None: |
| if dist.include_dirs is None: |
| dist.include_dirs = [] |
| dist.include_dirs.extend(include_dirs) |
| else: |
| self.include_dirs.extend(include_dirs) |
| |
| def add_headers(self,*files): |
| """Add installable headers to configuration. |
| |
| Add the given sequence of files to the beginning of the headers list. |
| By default, headers will be installed under <python- |
| include>/<self.name.replace('.','/')>/ directory. If an item of files |
| is a tuple, then its first argument specifies the actual installation |
| location relative to the <python-include> path. |
| |
| Parameters |
| ---------- |
| files : str or seq |
| Argument(s) can be either: |
| |
| * 2-sequence (<includedir suffix>,<path to header file(s)>) |
| * path(s) to header file(s) where python includedir suffix will |
| default to package name. |
| """ |
| headers = [] |
| for path in files: |
| if is_string(path): |
| [headers.append((self.name, p)) for p in self.paths(path)] |
| else: |
| if not isinstance(path, (tuple, list)) or len(path) != 2: |
| raise TypeError(repr(path)) |
| [headers.append((path[0], p)) for p in self.paths(path[1])] |
| dist = self.get_distribution() |
| if dist is not None: |
| if dist.headers is None: |
| dist.headers = [] |
| dist.headers.extend(headers) |
| else: |
| self.headers.extend(headers) |
| |
| def paths(self,*paths,**kws): |
| """Apply glob to paths and prepend local_path if needed. |
| |
| Applies glob.glob(...) to each path in the sequence (if needed) and |
| pre-pends the local_path if needed. Because this is called on all |
| source lists, this allows wildcard characters to be specified in lists |
| of sources for extension modules and libraries and scripts and allows |
| path-names be relative to the source directory. |
| |
| """ |
| include_non_existing = kws.get('include_non_existing', True) |
| return gpaths(paths, |
| local_path = self.local_path, |
| include_non_existing=include_non_existing) |
| |
| def _fix_paths_dict(self, kw): |
| for k in kw.keys(): |
| v = kw[k] |
| if k in ['sources', 'depends', 'include_dirs', 'library_dirs', |
| 'module_dirs', 'extra_objects']: |
| new_v = self.paths(v) |
| kw[k] = new_v |
| |
| def add_extension(self,name,sources,**kw): |
| """Add extension to configuration. |
| |
| Create and add an Extension instance to the ext_modules list. This |
| method also takes the following optional keyword arguments that are |
| passed on to the Extension constructor. |
| |
| Parameters |
| ---------- |
| name : str |
| name of the extension |
| sources : seq |
| list of the sources. The list of sources may contain functions |
| (called source generators) which must take an extension instance |
| and a build directory as inputs and return a source file or list of |
| source files or None. If None is returned then no sources are |
| generated. If the Extension instance has no sources after |
| processing all source generators, then no extension module is |
| built. |
| include_dirs : |
| define_macros : |
| undef_macros : |
| library_dirs : |
| libraries : |
| runtime_library_dirs : |
| extra_objects : |
| extra_compile_args : |
| extra_link_args : |
| extra_f77_compile_args : |
| extra_f90_compile_args : |
| export_symbols : |
| swig_opts : |
| depends : |
| The depends list contains paths to files or directories that the |
| sources of the extension module depend on. If any path in the |
| depends list is newer than the extension module, then the module |
| will be rebuilt. |
| language : |
| f2py_options : |
| module_dirs : |
| extra_info : dict or list |
| dict or list of dict of keywords to be appended to keywords. |
| |
| Notes |
| ----- |
| The self.paths(...) method is applied to all lists that may contain |
| paths. |
| """ |
| ext_args = copy.copy(kw) |
| ext_args['name'] = dot_join(self.name, name) |
| ext_args['sources'] = sources |
| |
| if 'extra_info' in ext_args: |
| extra_info = ext_args['extra_info'] |
| del ext_args['extra_info'] |
| if isinstance(extra_info, dict): |
| extra_info = [extra_info] |
| for info in extra_info: |
| assert isinstance(info, dict), repr(info) |
| dict_append(ext_args,**info) |
| |
| self._fix_paths_dict(ext_args) |
| |
| # Resolve out-of-tree dependencies |
| libraries = ext_args.get('libraries', []) |
| libnames = [] |
| ext_args['libraries'] = [] |
| for libname in libraries: |
| if isinstance(libname, tuple): |
| self._fix_paths_dict(libname[1]) |
| |
| # Handle library names of the form libname@relative/path/to/library |
| if '@' in libname: |
| lname, lpath = libname.split('@', 1) |
| lpath = os.path.abspath(njoin(self.local_path, lpath)) |
| if os.path.isdir(lpath): |
| c = self.get_subpackage(None, lpath, |
| caller_level = 2) |
| if isinstance(c, Configuration): |
| c = c.todict() |
| for l in [l[0] for l in c.get('libraries', [])]: |
| llname = l.split('__OF__', 1)[0] |
| if llname == lname: |
| c.pop('name', None) |
| dict_append(ext_args,**c) |
| break |
| continue |
| libnames.append(libname) |
| |
| ext_args['libraries'] = libnames + ext_args['libraries'] |
| ext_args['define_macros'] = \ |
| self.define_macros + ext_args.get('define_macros', []) |
| |
| from numpy.distutils.core import Extension |
| ext = Extension(**ext_args) |
| self.ext_modules.append(ext) |
| |
| dist = self.get_distribution() |
| if dist is not None: |
| self.warn('distutils distribution has been initialized,'\ |
| ' it may be too late to add an extension '+name) |
| return ext |
| |
| def add_library(self,name,sources,**build_info): |
| """ |
| Add library to configuration. |
| |
| Parameters |
| ---------- |
| name : str |
| Name of the extension. |
| sources : sequence |
| List of the sources. The list of sources may contain functions |
| (called source generators) which must take an extension instance |
| and a build directory as inputs and return a source file or list of |
| source files or None. If None is returned then no sources are |
| generated. If the Extension instance has no sources after |
| processing all source generators, then no extension module is |
| built. |
| build_info : dict, optional |
| The following keys are allowed: |
| |
| * depends |
| * macros |
| * include_dirs |
| * extra_compiler_args |
| * extra_f77_compile_args |
| * extra_f90_compile_args |
| * f2py_options |
| * language |
| |
| """ |
| self._add_library(name, sources, None, build_info) |
| |
| dist = self.get_distribution() |
| if dist is not None: |
| self.warn('distutils distribution has been initialized,'\ |
| ' it may be too late to add a library '+ name) |
| |
| def _add_library(self, name, sources, install_dir, build_info): |
| """Common implementation for add_library and add_installed_library. Do |
| not use directly""" |
| build_info = copy.copy(build_info) |
| build_info['sources'] = sources |
| |
| # Sometimes, depends is not set up to an empty list by default, and if |
| # depends is not given to add_library, distutils barfs (#1134) |
| if not 'depends' in build_info: |
| build_info['depends'] = [] |
| |
| self._fix_paths_dict(build_info) |
| |
| # Add to libraries list so that it is build with build_clib |
| self.libraries.append((name, build_info)) |
| |
| def add_installed_library(self, name, sources, install_dir, build_info=None): |
| """ |
| Similar to add_library, but the specified library is installed. |
| |
| Most C libraries used with `distutils` are only used to build python |
| extensions, but libraries built through this method will be installed |
| so that they can be reused by third-party packages. |
| |
| Parameters |
| ---------- |
| name : str |
| Name of the installed library. |
| sources : sequence |
| List of the library's source files. See `add_library` for details. |
| install_dir : str |
| Path to install the library, relative to the current sub-package. |
| build_info : dict, optional |
| The following keys are allowed: |
| |
| * depends |
| * macros |
| * include_dirs |
| * extra_compiler_args |
| * extra_f77_compile_args |
| * extra_f90_compile_args |
| * f2py_options |
| * language |
| |
| Returns |
| ------- |
| None |
| |
| See Also |
| -------- |
| add_library, add_npy_pkg_config, get_info |
| |
| Notes |
| ----- |
| The best way to encode the options required to link against the specified |
| C libraries is to use a "libname.ini" file, and use `get_info` to |
| retrieve the required options (see `add_npy_pkg_config` for more |
| information). |
| |
| """ |
| if not build_info: |
| build_info = {} |
| |
| install_dir = os.path.join(self.package_path, install_dir) |
| self._add_library(name, sources, install_dir, build_info) |
| self.installed_libraries.append(InstallableLib(name, build_info, install_dir)) |
| |
| def add_npy_pkg_config(self, template, install_dir, subst_dict=None): |
| """ |
| Generate and install a npy-pkg config file from a template. |
| |
| The config file generated from `template` is installed in the |
| given install directory, using `subst_dict` for variable substitution. |
| |
| Parameters |
| ---------- |
| template : str |
| The path of the template, relatively to the current package path. |
| install_dir : str |
| Where to install the npy-pkg config file, relatively to the current |
| package path. |
| subst_dict : dict, optional |
| If given, any string of the form ``@key@`` will be replaced by |
| ``subst_dict[key]`` in the template file when installed. The install |
| prefix is always available through the variable ``@prefix@``, since the |
| install prefix is not easy to get reliably from setup.py. |
| |
| See also |
| -------- |
| add_installed_library, get_info |
| |
| Notes |
| ----- |
| This works for both standard installs and in-place builds, i.e. the |
| ``@prefix@`` refer to the source directory for in-place builds. |
| |
| Examples |
| -------- |
| :: |
| |
| config.add_npy_pkg_config('foo.ini.in', 'lib', {'foo': bar}) |
| |
| Assuming the foo.ini.in file has the following content:: |
| |
| [meta] |
| Name=@foo@ |
| Version=1.0 |
| Description=dummy description |
| |
| [default] |
| Cflags=-I@prefix@/include |
| Libs= |
| |
| The generated file will have the following content:: |
| |
| [meta] |
| Name=bar |
| Version=1.0 |
| Description=dummy description |
| |
| [default] |
| Cflags=-Iprefix_dir/include |
| Libs= |
| |
| and will be installed as foo.ini in the 'lib' subpath. |
| |
| When cross-compiling with numpy distutils, it might be necessary to |
| use modified npy-pkg-config files. Using the default/generated files |
| will link with the host libraries (i.e. libnpymath.a). For |
| cross-compilation you of-course need to link with target libraries, |
| while using the host Python installation. |
| |
| You can copy out the numpy/core/lib/npy-pkg-config directory, add a |
| pkgdir value to the .ini files and set NPY_PKG_CONFIG_PATH environment |
| variable to point to the directory with the modified npy-pkg-config |
| files. |
| |
| Example npymath.ini modified for cross-compilation:: |
| |
| [meta] |
| Name=npymath |
| Description=Portable, core math library implementing C99 standard |
| Version=0.1 |
| |
| [variables] |
| pkgname=numpy.core |
| pkgdir=/build/arm-linux-gnueabi/sysroot/usr/lib/python3.7/site-packages/numpy/core |
| prefix=${pkgdir} |
| libdir=${prefix}/lib |
| includedir=${prefix}/include |
| |
| [default] |
| Libs=-L${libdir} -lnpymath |
| Cflags=-I${includedir} |
| Requires=mlib |
| |
| [msvc] |
| Libs=/LIBPATH:${libdir} npymath.lib |
| Cflags=/INCLUDE:${includedir} |
| Requires=mlib |
| |
| """ |
| if subst_dict is None: |
| subst_dict = {} |
| template = os.path.join(self.package_path, template) |
| |
| if self.name in self.installed_pkg_config: |
| self.installed_pkg_config[self.name].append((template, install_dir, |
| subst_dict)) |
| else: |
| self.installed_pkg_config[self.name] = [(template, install_dir, |
| subst_dict)] |
| |
| |
| def add_scripts(self,*files): |
| """Add scripts to configuration. |
| |
| Add the sequence of files to the beginning of the scripts list. |
| Scripts will be installed under the <prefix>/bin/ directory. |
| |
| """ |
| scripts = self.paths(files) |
| dist = self.get_distribution() |
| if dist is not None: |
| if dist.scripts is None: |
| dist.scripts = [] |
| dist.scripts.extend(scripts) |
| else: |
| self.scripts.extend(scripts) |
| |
| def dict_append(self,**dict): |
| for key in self.list_keys: |
| a = getattr(self, key) |
| a.extend(dict.get(key, [])) |
| for key in self.dict_keys: |
| a = getattr(self, key) |
| a.update(dict.get(key, {})) |
| known_keys = self.list_keys + self.dict_keys + self.extra_keys |
| for key in dict.keys(): |
| if key not in known_keys: |
| a = getattr(self, key, None) |
| if a and a==dict[key]: continue |
| self.warn('Inheriting attribute %r=%r from %r' \ |
| % (key, dict[key], dict.get('name', '?'))) |
| setattr(self, key, dict[key]) |
| self.extra_keys.append(key) |
| elif key in self.extra_keys: |
| self.info('Ignoring attempt to set %r (from %r to %r)' \ |
| % (key, getattr(self, key), dict[key])) |
| elif key in known_keys: |
| # key is already processed above |
| pass |
| else: |
| raise ValueError("Don't know about key=%r" % (key)) |
| |
| def __str__(self): |
| from pprint import pformat |
| known_keys = self.list_keys + self.dict_keys + self.extra_keys |
| s = '<'+5*'-' + '\n' |
| s += 'Configuration of '+self.name+':\n' |
| known_keys.sort() |
| for k in known_keys: |
| a = getattr(self, k, None) |
| if a: |
| s += '%s = %s\n' % (k, pformat(a)) |
| s += 5*'-' + '>' |
| return s |
| |
| def get_config_cmd(self): |
| """ |
| Returns the numpy.distutils config command instance. |
| """ |
| cmd = get_cmd('config') |
| cmd.ensure_finalized() |
| cmd.dump_source = 0 |
| cmd.noisy = 0 |
| old_path = os.environ.get('PATH') |
| if old_path: |
| path = os.pathsep.join(['.', old_path]) |
| os.environ['PATH'] = path |
| return cmd |
| |
| def get_build_temp_dir(self): |
| """ |
| Return a path to a temporary directory where temporary files should be |
| placed. |
| """ |
| cmd = get_cmd('build') |
| cmd.ensure_finalized() |
| return cmd.build_temp |
| |
| def have_f77c(self): |
| """Check for availability of Fortran 77 compiler. |
| |
| Use it inside source generating function to ensure that |
| setup distribution instance has been initialized. |
| |
| Notes |
| ----- |
| True if a Fortran 77 compiler is available (because a simple Fortran 77 |
| code was able to be compiled successfully). |
| """ |
| simple_fortran_subroutine = ''' |
| subroutine simple |
| end |
| ''' |
| config_cmd = self.get_config_cmd() |
| flag = config_cmd.try_compile(simple_fortran_subroutine, lang='f77') |
| return flag |
| |
| def have_f90c(self): |
| """Check for availability of Fortran 90 compiler. |
| |
| Use it inside source generating function to ensure that |
| setup distribution instance has been initialized. |
| |
| Notes |
| ----- |
| True if a Fortran 90 compiler is available (because a simple Fortran |
| 90 code was able to be compiled successfully) |
| """ |
| simple_fortran_subroutine = ''' |
| subroutine simple |
| end |
| ''' |
| config_cmd = self.get_config_cmd() |
| flag = config_cmd.try_compile(simple_fortran_subroutine, lang='f90') |
| return flag |
| |
| def append_to(self, extlib): |
| """Append libraries, include_dirs to extension or library item. |
| """ |
| if is_sequence(extlib): |
| lib_name, build_info = extlib |
| dict_append(build_info, |
| libraries=self.libraries, |
| include_dirs=self.include_dirs) |
| else: |
| from numpy.distutils.core import Extension |
| assert isinstance(extlib, Extension), repr(extlib) |
| extlib.libraries.extend(self.libraries) |
| extlib.include_dirs.extend(self.include_dirs) |
| |
| def _get_svn_revision(self, path): |
| """Return path's SVN revision number. |
| """ |
| try: |
| output = subprocess.check_output(['svnversion'], cwd=path) |
| except (subprocess.CalledProcessError, OSError): |
| pass |
| else: |
| m = re.match(rb'(?P<revision>\d+)', output) |
| if m: |
| return int(m.group('revision')) |
| |
| if sys.platform=='win32' and os.environ.get('SVN_ASP_DOT_NET_HACK', None): |
| entries = njoin(path, '_svn', 'entries') |
| else: |
| entries = njoin(path, '.svn', 'entries') |
| if os.path.isfile(entries): |
| with open(entries) as f: |
| fstr = f.read() |
| if fstr[:5] == '<?xml': # pre 1.4 |
| m = re.search(r'revision="(?P<revision>\d+)"', fstr) |
| if m: |
| return int(m.group('revision')) |
| else: # non-xml entries file --- check to be sure that |
| m = re.search(r'dir[\n\r]+(?P<revision>\d+)', fstr) |
| if m: |
| return int(m.group('revision')) |
| return None |
| |
| def _get_hg_revision(self, path): |
| """Return path's Mercurial revision number. |
| """ |
| try: |
| output = subprocess.check_output( |
| ['hg', 'identify', '--num'], cwd=path) |
| except (subprocess.CalledProcessError, OSError): |
| pass |
| else: |
| m = re.match(rb'(?P<revision>\d+)', output) |
| if m: |
| return int(m.group('revision')) |
| |
| branch_fn = njoin(path, '.hg', 'branch') |
| branch_cache_fn = njoin(path, '.hg', 'branch.cache') |
| |
| if os.path.isfile(branch_fn): |
| branch0 = None |
| with open(branch_fn) as f: |
| revision0 = f.read().strip() |
| |
| branch_map = {} |
| with open(branch_cache_fn) as f: |
| for line in f: |
| branch1, revision1 = line.split()[:2] |
| if revision1==revision0: |
| branch0 = branch1 |
| try: |
| revision1 = int(revision1) |
| except ValueError: |
| continue |
| branch_map[branch1] = revision1 |
| |
| return branch_map.get(branch0) |
| |
| return None |
| |
| |
| def get_version(self, version_file=None, version_variable=None): |
| """Try to get version string of a package. |
| |
| Return a version string of the current package or None if the version |
| information could not be detected. |
| |
| Notes |
| ----- |
| This method scans files named |
| __version__.py, <packagename>_version.py, version.py, and |
| __svn_version__.py for string variables version, __version__, and |
| <packagename>_version, until a version number is found. |
| """ |
| version = getattr(self, 'version', None) |
| if version is not None: |
| return version |
| |
| # Get version from version file. |
| if version_file is None: |
| files = ['__version__.py', |
| self.name.split('.')[-1]+'_version.py', |
| 'version.py', |
| '__svn_version__.py', |
| '__hg_version__.py'] |
| else: |
| files = [version_file] |
| if version_variable is None: |
| version_vars = ['version', |
| '__version__', |
| self.name.split('.')[-1]+'_version'] |
| else: |
| version_vars = [version_variable] |
| for f in files: |
| fn = njoin(self.local_path, f) |
| if os.path.isfile(fn): |
| info = ('.py', 'U', 1) |
| name = os.path.splitext(os.path.basename(fn))[0] |
| n = dot_join(self.name, name) |
| try: |
| version_module = exec_mod_from_location( |
| '_'.join(n.split('.')), fn) |
| except ImportError as e: |
| self.warn(str(e)) |
| version_module = None |
| if version_module is None: |
| continue |
| |
| for a in version_vars: |
| version = getattr(version_module, a, None) |
| if version is not None: |
| break |
| |
| # Try if versioneer module |
| try: |
| version = version_module.get_versions()['version'] |
| except AttributeError: |
| pass |
| |
| if version is not None: |
| break |
| |
| if version is not None: |
| self.version = version |
| return version |
| |
| # Get version as SVN or Mercurial revision number |
| revision = self._get_svn_revision(self.local_path) |
| if revision is None: |
| revision = self._get_hg_revision(self.local_path) |
| |
| if revision is not None: |
| version = str(revision) |
| self.version = version |
| |
| return version |
| |
| def make_svn_version_py(self, delete=True): |
| """Appends a data function to the data_files list that will generate |
| __svn_version__.py file to the current package directory. |
| |
| Generate package __svn_version__.py file from SVN revision number, |
| it will be removed after python exits but will be available |
| when sdist, etc commands are executed. |
| |
| Notes |
| ----- |
| If __svn_version__.py existed before, nothing is done. |
| |
| This is |
| intended for working with source directories that are in an SVN |
| repository. |
| """ |
| target = njoin(self.local_path, '__svn_version__.py') |
| revision = self._get_svn_revision(self.local_path) |
| if os.path.isfile(target) or revision is None: |
| return |
| else: |
| def generate_svn_version_py(): |
| if not os.path.isfile(target): |
| version = str(revision) |
| self.info('Creating %s (version=%r)' % (target, version)) |
| with open(target, 'w') as f: |
| f.write('version = %r\n' % (version)) |
| |
| def rm_file(f=target,p=self.info): |
| if delete: |
| try: os.remove(f); p('removed '+f) |
| except OSError: pass |
| try: os.remove(f+'c'); p('removed '+f+'c') |
| except OSError: pass |
| |
| atexit.register(rm_file) |
| |
| return target |
| |
| self.add_data_files(('', generate_svn_version_py())) |
| |
| def make_hg_version_py(self, delete=True): |
| """Appends a data function to the data_files list that will generate |
| __hg_version__.py file to the current package directory. |
| |
| Generate package __hg_version__.py file from Mercurial revision, |
| it will be removed after python exits but will be available |
| when sdist, etc commands are executed. |
| |
| Notes |
| ----- |
| If __hg_version__.py existed before, nothing is done. |
| |
| This is intended for working with source directories that are |
| in an Mercurial repository. |
| """ |
| target = njoin(self.local_path, '__hg_version__.py') |
| revision = self._get_hg_revision(self.local_path) |
| if os.path.isfile(target) or revision is None: |
| return |
| else: |
| def generate_hg_version_py(): |
| if not os.path.isfile(target): |
| version = str(revision) |
| self.info('Creating %s (version=%r)' % (target, version)) |
| with open(target, 'w') as f: |
| f.write('version = %r\n' % (version)) |
| |
| def rm_file(f=target,p=self.info): |
| if delete: |
| try: os.remove(f); p('removed '+f) |
| except OSError: pass |
| try: os.remove(f+'c'); p('removed '+f+'c') |
| except OSError: pass |
| |
| atexit.register(rm_file) |
| |
| return target |
| |
| self.add_data_files(('', generate_hg_version_py())) |
| |
| def make_config_py(self,name='__config__'): |
| """Generate package __config__.py file containing system_info |
| information used during building the package. |
| |
| This file is installed to the |
| package installation directory. |
| |
| """ |
| self.py_modules.append((self.name, name, generate_config_py)) |
| |
| def get_info(self,*names): |
| """Get resources information. |
| |
| Return information (from system_info.get_info) for all of the names in |
| the argument list in a single dictionary. |
| """ |
| from .system_info import get_info, dict_append |
| info_dict = {} |
| for a in names: |
| dict_append(info_dict,**get_info(a)) |
| return info_dict |
| |
| |
| def get_cmd(cmdname, _cache={}): |
| if cmdname not in _cache: |
| import distutils.core |
| dist = distutils.core._setup_distribution |
| if dist is None: |
| from distutils.errors import DistutilsInternalError |
| raise DistutilsInternalError( |
| 'setup distribution instance not initialized') |
| cmd = dist.get_command_obj(cmdname) |
| _cache[cmdname] = cmd |
| return _cache[cmdname] |
| |
| def get_numpy_include_dirs(): |
| # numpy_include_dirs are set by numpy/core/setup.py, otherwise [] |
| include_dirs = Configuration.numpy_include_dirs[:] |
| if not include_dirs: |
| import numpy |
| include_dirs = [ numpy.get_include() ] |
| # else running numpy/core/setup.py |
| return include_dirs |
| |
| def get_npy_pkg_dir(): |
| """Return the path where to find the npy-pkg-config directory. |
| |
| If the NPY_PKG_CONFIG_PATH environment variable is set, the value of that |
| is returned. Otherwise, a path inside the location of the numpy module is |
| returned. |
| |
| The NPY_PKG_CONFIG_PATH can be useful when cross-compiling, maintaining |
| customized npy-pkg-config .ini files for the cross-compilation |
| environment, and using them when cross-compiling. |
| |
| """ |
| d = os.environ.get('NPY_PKG_CONFIG_PATH') |
| if d is not None: |
| return d |
| spec = importlib.util.find_spec('numpy') |
| d = os.path.join(os.path.dirname(spec.origin), |
| 'core', 'lib', 'npy-pkg-config') |
| return d |
| |
| def get_pkg_info(pkgname, dirs=None): |
| """ |
| Return library info for the given package. |
| |
| Parameters |
| ---------- |
| pkgname : str |
| Name of the package (should match the name of the .ini file, without |
| the extension, e.g. foo for the file foo.ini). |
| dirs : sequence, optional |
| If given, should be a sequence of additional directories where to look |
| for npy-pkg-config files. Those directories are searched prior to the |
| NumPy directory. |
| |
| Returns |
| ------- |
| pkginfo : class instance |
| The `LibraryInfo` instance containing the build information. |
| |
| Raises |
| ------ |
| PkgNotFound |
| If the package is not found. |
| |
| See Also |
| -------- |
| Configuration.add_npy_pkg_config, Configuration.add_installed_library, |
| get_info |
| |
| """ |
| from numpy.distutils.npy_pkg_config import read_config |
| |
| if dirs: |
| dirs.append(get_npy_pkg_dir()) |
| else: |
| dirs = [get_npy_pkg_dir()] |
| return read_config(pkgname, dirs) |
| |
| def get_info(pkgname, dirs=None): |
| """ |
| Return an info dict for a given C library. |
| |
| The info dict contains the necessary options to use the C library. |
| |
| Parameters |
| ---------- |
| pkgname : str |
| Name of the package (should match the name of the .ini file, without |
| the extension, e.g. foo for the file foo.ini). |
| dirs : sequence, optional |
| If given, should be a sequence of additional directories where to look |
| for npy-pkg-config files. Those directories are searched prior to the |
| NumPy directory. |
| |
| Returns |
| ------- |
| info : dict |
| The dictionary with build information. |
| |
| Raises |
| ------ |
| PkgNotFound |
| If the package is not found. |
| |
| See Also |
| -------- |
| Configuration.add_npy_pkg_config, Configuration.add_installed_library, |
| get_pkg_info |
| |
| Examples |
| -------- |
| To get the necessary information for the npymath library from NumPy: |
| |
| >>> npymath_info = np.distutils.misc_util.get_info('npymath') |
| >>> npymath_info #doctest: +SKIP |
| {'define_macros': [], 'libraries': ['npymath'], 'library_dirs': |
| ['.../numpy/core/lib'], 'include_dirs': ['.../numpy/core/include']} |
| |
| This info dict can then be used as input to a `Configuration` instance:: |
| |
| config.add_extension('foo', sources=['foo.c'], extra_info=npymath_info) |
| |
| """ |
| from numpy.distutils.npy_pkg_config import parse_flags |
| pkg_info = get_pkg_info(pkgname, dirs) |
| |
| # Translate LibraryInfo instance into a build_info dict |
| info = parse_flags(pkg_info.cflags()) |
| for k, v in parse_flags(pkg_info.libs()).items(): |
| info[k].extend(v) |
| |
| # add_extension extra_info argument is ANAL |
| info['define_macros'] = info['macros'] |
| del info['macros'] |
| del info['ignored'] |
| |
| return info |
| |
| def is_bootstrapping(): |
| import builtins |
| |
| try: |
| builtins.__NUMPY_SETUP__ |
| return True |
| except AttributeError: |
| return False |
| |
| |
| ######################### |
| |
| def default_config_dict(name = None, parent_name = None, local_path=None): |
| """Return a configuration dictionary for usage in |
| configuration() function defined in file setup_<name>.py. |
| """ |
| import warnings |
| warnings.warn('Use Configuration(%r,%r,top_path=%r) instead of '\ |
| 'deprecated default_config_dict(%r,%r,%r)' |
| % (name, parent_name, local_path, |
| name, parent_name, local_path, |
| ), stacklevel=2) |
| c = Configuration(name, parent_name, local_path) |
| return c.todict() |
| |
| |
| def dict_append(d, **kws): |
| for k, v in kws.items(): |
| if k in d: |
| ov = d[k] |
| if isinstance(ov, str): |
| d[k] = v |
| else: |
| d[k].extend(v) |
| else: |
| d[k] = v |
| |
| def appendpath(prefix, path): |
| if os.path.sep != '/': |
| prefix = prefix.replace('/', os.path.sep) |
| path = path.replace('/', os.path.sep) |
| drive = '' |
| if os.path.isabs(path): |
| drive = os.path.splitdrive(prefix)[0] |
| absprefix = os.path.splitdrive(os.path.abspath(prefix))[1] |
| pathdrive, path = os.path.splitdrive(path) |
| d = os.path.commonprefix([absprefix, path]) |
| if os.path.join(absprefix[:len(d)], absprefix[len(d):]) != absprefix \ |
| or os.path.join(path[:len(d)], path[len(d):]) != path: |
| # Handle invalid paths |
| d = os.path.dirname(d) |
| subpath = path[len(d):] |
| if os.path.isabs(subpath): |
| subpath = subpath[1:] |
| else: |
| subpath = path |
| return os.path.normpath(njoin(drive + prefix, subpath)) |
| |
| def generate_config_py(target): |
| """Generate config.py file containing system_info information |
| used during building the package. |
| |
| Usage: |
| config['py_modules'].append((packagename, '__config__',generate_config_py)) |
| """ |
| from numpy.distutils.system_info import system_info |
| from distutils.dir_util import mkpath |
| mkpath(os.path.dirname(target)) |
| with open(target, 'w') as f: |
| f.write('# This file is generated by numpy\'s %s\n' % (os.path.basename(sys.argv[0]))) |
| f.write('# It contains system_info results at the time of building this package.\n') |
| f.write('__all__ = ["get_info","show"]\n\n') |
| |
| # For gfortran+msvc combination, extra shared libraries may exist |
| f.write(textwrap.dedent(""" |
| import os |
| import sys |
| |
| extra_dll_dir = os.path.join(os.path.dirname(__file__), '.libs') |
| |
| if sys.platform == 'win32' and os.path.isdir(extra_dll_dir): |
| os.add_dll_directory(extra_dll_dir) |
| |
| """)) |
| |
| for k, i in system_info.saved_results.items(): |
| f.write('%s=%r\n' % (k, i)) |
| f.write(textwrap.dedent(r''' |
| def get_info(name): |
| g = globals() |
| return g.get(name, g.get(name + "_info", {})) |
| |
| def show(): |
| """ |
| Show libraries in the system on which NumPy was built. |
| |
| Print information about various resources (libraries, library |
| directories, include directories, etc.) in the system on which |
| NumPy was built. |
| |
| See Also |
| -------- |
| get_include : Returns the directory containing NumPy C |
| header files. |
| |
| Notes |
| ----- |
| 1. Classes specifying the information to be printed are defined |
| in the `numpy.distutils.system_info` module. |
| |
| Information may include: |
| |
| * ``language``: language used to write the libraries (mostly |
| C or f77) |
| * ``libraries``: names of libraries found in the system |
| * ``library_dirs``: directories containing the libraries |
| * ``include_dirs``: directories containing library header files |
| * ``src_dirs``: directories containing library source files |
| * ``define_macros``: preprocessor macros used by |
| ``distutils.setup`` |
| * ``baseline``: minimum CPU features required |
| * ``found``: dispatched features supported in the system |
| * ``not found``: dispatched features that are not supported |
| in the system |
| |
| 2. NumPy BLAS/LAPACK Installation Notes |
| |
| Installing a numpy wheel (``pip install numpy`` or force it |
| via ``pip install numpy --only-binary :numpy: numpy``) includes |
| an OpenBLAS implementation of the BLAS and LAPACK linear algebra |
| APIs. In this case, ``library_dirs`` reports the original build |
| time configuration as compiled with gcc/gfortran; at run time |
| the OpenBLAS library is in |
| ``site-packages/numpy.libs/`` (linux), or |
| ``site-packages/numpy/.dylibs/`` (macOS), or |
| ``site-packages/numpy/.libs/`` (windows). |
| |
| Installing numpy from source |
| (``pip install numpy --no-binary numpy``) searches for BLAS and |
| LAPACK dynamic link libraries at build time as influenced by |
| environment variables NPY_BLAS_LIBS, NPY_CBLAS_LIBS, and |
| NPY_LAPACK_LIBS; or NPY_BLAS_ORDER and NPY_LAPACK_ORDER; |
| or the optional file ``~/.numpy-site.cfg``. |
| NumPy remembers those locations and expects to load the same |
| libraries at run-time. |
| In NumPy 1.21+ on macOS, 'accelerate' (Apple's Accelerate BLAS |
| library) is in the default build-time search order after |
| 'openblas'. |
| |
| Examples |
| -------- |
| >>> import numpy as np |
| >>> np.show_config() |
| blas_opt_info: |
| language = c |
| define_macros = [('HAVE_CBLAS', None)] |
| libraries = ['openblas', 'openblas'] |
| library_dirs = ['/usr/local/lib'] |
| """ |
| from numpy.core._multiarray_umath import ( |
| __cpu_features__, __cpu_baseline__, __cpu_dispatch__ |
| ) |
| for name,info_dict in globals().items(): |
| if name[0] == "_" or type(info_dict) is not type({}): continue |
| print(name + ":") |
| if not info_dict: |
| print(" NOT AVAILABLE") |
| for k,v in info_dict.items(): |
| v = str(v) |
| if k == "sources" and len(v) > 200: |
| v = v[:60] + " ...\n... " + v[-60:] |
| print(" %s = %s" % (k,v)) |
| |
| features_found, features_not_found = [], [] |
| for feature in __cpu_dispatch__: |
| if __cpu_features__[feature]: |
| features_found.append(feature) |
| else: |
| features_not_found.append(feature) |
| |
| print("Supported SIMD extensions in this NumPy install:") |
| print(" baseline = %s" % (','.join(__cpu_baseline__))) |
| print(" found = %s" % (','.join(features_found))) |
| print(" not found = %s" % (','.join(features_not_found))) |
| |
| ''')) |
| |
| return target |
| |
| def msvc_version(compiler): |
| """Return version major and minor of compiler instance if it is |
| MSVC, raise an exception otherwise.""" |
| if not compiler.compiler_type == "msvc": |
| raise ValueError("Compiler instance is not msvc (%s)"\ |
| % compiler.compiler_type) |
| return compiler._MSVCCompiler__version |
| |
| def get_build_architecture(): |
| # Importing distutils.msvccompiler triggers a warning on non-Windows |
| # systems, so delay the import to here. |
| from distutils.msvccompiler import get_build_architecture |
| return get_build_architecture() |
| |
| |
| _cxx_ignore_flags = {'-Werror=implicit-function-declaration', '-std=c99'} |
| |
| |
| def sanitize_cxx_flags(cxxflags): |
| ''' |
| Some flags are valid for C but not C++. Prune them. |
| ''' |
| return [flag for flag in cxxflags if flag not in _cxx_ignore_flags] |
| |
| |
| def exec_mod_from_location(modname, modfile): |
| ''' |
| Use importlib machinery to import a module `modname` from the file |
| `modfile`. Depending on the `spec.loader`, the module may not be |
| registered in sys.modules. |
| ''' |
| spec = importlib.util.spec_from_file_location(modname, modfile) |
| foo = importlib.util.module_from_spec(spec) |
| spec.loader.exec_module(foo) |
| return foo |