You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
2494 lines
90 KiB
2494 lines
90 KiB
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
|