Skip to content

Commit 7b34e64

Browse files
jhunkelermperrin
authored andcommitted
Update ah_bootstrap.py
1 parent 47e3144 commit 7b34e64

File tree

1 file changed

+82
-94
lines changed

1 file changed

+82
-94
lines changed

ah_bootstrap.py

Lines changed: 82 additions & 94 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,14 @@
1919
contains an option called ``auto_use`` with a value of ``True``, it will
2020
automatically call the main function of this module called
2121
`use_astropy_helpers` (see that function's docstring for full details).
22-
Otherwise no further action is taken (however,
23-
``ah_bootstrap.use_astropy_helpers`` may be called manually from within the
24-
setup.py script).
22+
Otherwise no further action is taken and by default the system-installed version
23+
of astropy-helpers will be used (however, ``ah_bootstrap.use_astropy_helpers``
24+
may be called manually from within the setup.py script).
25+
26+
This behavior can also be controlled using the ``--auto-use`` and
27+
``--no-auto-use`` command-line flags. For clarity, an alias for
28+
``--no-auto-use`` is ``--use-system-astropy-helpers``, and we recommend using
29+
the latter if needed.
2530
2631
Additional options in the ``[ah_boostrap]`` section of setup.cfg have the same
2732
names as the arguments to `use_astropy_helpers`, and can be used to configure
@@ -33,62 +38,52 @@
3338

3439
import contextlib
3540
import errno
36-
import imp
3741
import io
3842
import locale
3943
import os
4044
import re
4145
import subprocess as sp
4246
import sys
4347

48+
__minimum_python_version__ = (3, 5)
49+
50+
if sys.version_info < __minimum_python_version__:
51+
print("ERROR: Python {} or later is required by astropy-helpers".format(
52+
__minimum_python_version__))
53+
sys.exit(1)
54+
4455
try:
4556
from ConfigParser import ConfigParser, RawConfigParser
4657
except ImportError:
4758
from configparser import ConfigParser, RawConfigParser
4859

4960

50-
if sys.version_info[0] < 3:
51-
_str_types = (str, unicode)
52-
_text_type = unicode
53-
PY3 = False
54-
else:
55-
_str_types = (str, bytes)
56-
_text_type = str
57-
PY3 = True
61+
_str_types = (str, bytes)
5862

5963

6064
# What follows are several import statements meant to deal with install-time
6165
# issues with either missing or misbehaving pacakges (including making sure
6266
# setuptools itself is installed):
6367

68+
# Check that setuptools 1.0 or later is present
69+
from distutils.version import LooseVersion
6470

65-
# Some pre-setuptools checks to ensure that either distribute or setuptools >=
66-
# 0.7 is used (over pre-distribute setuptools) if it is available on the path;
67-
# otherwise the latest setuptools will be downloaded and bootstrapped with
68-
# ``ez_setup.py``. This used to be included in a separate file called
69-
# setuptools_bootstrap.py; but it was combined into ah_bootstrap.py
7071
try:
71-
import pkg_resources
72-
_setuptools_req = pkg_resources.Requirement.parse('setuptools>=0.7')
73-
# This may raise a DistributionNotFound in which case no version of
74-
# setuptools or distribute is properly installed
75-
_setuptools = pkg_resources.get_distribution('setuptools')
76-
if _setuptools not in _setuptools_req:
77-
# Older version of setuptools; check if we have distribute; again if
78-
# this results in DistributionNotFound we want to give up
79-
_distribute = pkg_resources.get_distribution('distribute')
80-
if _setuptools != _distribute:
81-
# It's possible on some pathological systems to have an old version
82-
# of setuptools and distribute on sys.path simultaneously; make
83-
# sure distribute is the one that's used
84-
sys.path.insert(1, _distribute.location)
85-
_distribute.activate()
86-
imp.reload(pkg_resources)
87-
except:
88-
# There are several types of exceptions that can occur here; if all else
89-
# fails bootstrap and use the bootstrapped version
90-
from ez_setup import use_setuptools
91-
use_setuptools()
72+
import setuptools
73+
assert LooseVersion(setuptools.__version__) >= LooseVersion('1.0')
74+
except (ImportError, AssertionError):
75+
print("ERROR: setuptools 1.0 or later is required by astropy-helpers")
76+
sys.exit(1)
77+
78+
# typing as a dependency for 1.6.1+ Sphinx causes issues when imported after
79+
# initializing submodule with ah_boostrap.py
80+
# See discussion and references in
81+
# https://github.com/astropy/astropy-helpers/issues/302
82+
83+
try:
84+
import typing # noqa
85+
except ImportError:
86+
pass
9287

9388

9489
# Note: The following import is required as a workaround to
@@ -97,7 +92,7 @@
9792
# later cause the TemporaryDirectory class defined in it to stop working when
9893
# used later on by setuptools
9994
try:
100-
import setuptools.py31compat
95+
import setuptools.py31compat # noqa
10196
except ImportError:
10297
pass
10398

@@ -126,7 +121,6 @@
126121

127122
from setuptools import Distribution
128123
from setuptools.package_index import PackageIndex
129-
from setuptools.sandbox import run_setup
130124

131125
from distutils import log
132126
from distutils.debug import DEBUG
@@ -135,6 +129,7 @@
135129
# TODO: Maybe enable checking for a specific version of astropy_helpers?
136130
DIST_NAME = 'astropy-helpers'
137131
PACKAGE_NAME = 'astropy_helpers'
132+
UPPER_VERSION_EXCLUSIVE = None
138133

139134
# Defaults for other options
140135
DOWNLOAD_IF_NEEDED = True
@@ -166,7 +161,7 @@ def __init__(self, path=None, index_url=None, use_git=None, offline=None,
166161
if not (isinstance(path, _str_types) or path is False):
167162
raise TypeError('path must be a string or False')
168163

169-
if PY3 and not isinstance(path, _text_type):
164+
if not isinstance(path, str):
170165
fs_encoding = sys.getfilesystemencoding()
171166
path = path.decode(fs_encoding) # path to unicode
172167

@@ -276,6 +271,18 @@ def parse_command_line(cls, argv=None):
276271
config['offline'] = True
277272
argv.remove('--offline')
278273

274+
if '--auto-use' in argv:
275+
config['auto_use'] = True
276+
argv.remove('--auto-use')
277+
278+
if '--no-auto-use' in argv:
279+
config['auto_use'] = False
280+
argv.remove('--no-auto-use')
281+
282+
if '--use-system-astropy-helpers' in argv:
283+
config['auto_use'] = False
284+
argv.remove('--use-system-astropy-helpers')
285+
279286
return config
280287

281288
def run(self):
@@ -453,9 +460,10 @@ def _directory_import(self):
453460
# setup.py exists we can generate it
454461
setup_py = os.path.join(path, 'setup.py')
455462
if os.path.isfile(setup_py):
456-
with _silence():
457-
run_setup(os.path.join(path, 'setup.py'),
458-
['egg_info'])
463+
# We use subprocess instead of run_setup from setuptools to
464+
# avoid segmentation faults - see the following for more details:
465+
# https://github.com/cython/cython/issues/2104
466+
sp.check_output([sys.executable, 'setup.py', 'egg_info'], cwd=path)
459467

460468
for dist in pkg_resources.find_distributions(path, True):
461469
# There should be only one...
@@ -490,16 +498,32 @@ def get_option_dict(self, command_name):
490498
if version:
491499
req = '{0}=={1}'.format(DIST_NAME, version)
492500
else:
493-
req = DIST_NAME
501+
if UPPER_VERSION_EXCLUSIVE is None:
502+
req = DIST_NAME
503+
else:
504+
req = '{0}<{1}'.format(DIST_NAME, UPPER_VERSION_EXCLUSIVE)
494505

495506
attrs = {'setup_requires': [req]}
496507

508+
# NOTE: we need to parse the config file (e.g. setup.cfg) to make sure
509+
# it honours the options set in the [easy_install] section, and we need
510+
# to explicitly fetch the requirement eggs as setup_requires does not
511+
# get honored in recent versions of setuptools:
512+
# https://github.com/pypa/setuptools/issues/1273
513+
497514
try:
498-
if DEBUG:
499-
_Distribution(attrs=attrs)
500-
else:
501-
with _silence():
502-
_Distribution(attrs=attrs)
515+
516+
context = _verbose if DEBUG else _silence
517+
with context():
518+
dist = _Distribution(attrs=attrs)
519+
try:
520+
dist.parse_config_files(ignore_option_errors=True)
521+
dist.fetch_build_eggs(req)
522+
except TypeError:
523+
# On older versions of setuptools, ignore_option_errors
524+
# doesn't exist, and the above two lines are not needed
525+
# so we can just continue
526+
pass
503527

504528
# If the setup_requires succeeded it will have added the new dist to
505529
# the main working_set
@@ -702,7 +726,7 @@ def _update_submodule(self, submodule, status):
702726
if self.offline:
703727
cmd.append('--no-fetch')
704728
elif status == 'U':
705-
raise _AHBoostrapSystemExit(
729+
raise _AHBootstrapSystemExit(
706730
'Error: Submodule {0} contains unresolved merge conflicts. '
707731
'Please complete or abandon any changes in the submodule so that '
708732
'it is in a usable state, then try again.'.format(submodule))
@@ -763,7 +787,7 @@ def run_cmd(cmd):
763787
msg = 'Command not found: `{0}`'.format(' '.join(cmd))
764788
raise _CommandNotFound(msg, cmd)
765789
else:
766-
raise _AHBoostrapSystemExit(
790+
raise _AHBootstrapSystemExit(
767791
'An unexpected error occurred when running the '
768792
'`{0}` command:\n{1}'.format(' '.join(cmd), str(e)))
769793

@@ -780,9 +804,9 @@ def run_cmd(cmd):
780804
stdio_encoding = 'latin1'
781805

782806
# Unlikely to fail at this point but even then let's be flexible
783-
if not isinstance(stdout, _text_type):
807+
if not isinstance(stdout, str):
784808
stdout = stdout.decode(stdio_encoding, 'replace')
785-
if not isinstance(stderr, _text_type):
809+
if not isinstance(stderr, str):
786810
stderr = stderr.decode(stdio_encoding, 'replace')
787811

788812
return (p.returncode, stdout, stderr)
@@ -835,6 +859,10 @@ def flush(self):
835859
pass
836860

837861

862+
@contextlib.contextmanager
863+
def _verbose():
864+
yield
865+
838866
@contextlib.contextmanager
839867
def _silence():
840868
"""A context manager that silences sys.stdout and sys.stderr."""
@@ -878,46 +906,6 @@ def __init__(self, *args):
878906
super(_AHBootstrapSystemExit, self).__init__(msg, *args[1:])
879907

880908

881-
if sys.version_info[:2] < (2, 7):
882-
# In Python 2.6 the distutils log does not log warnings, errors, etc. to
883-
# stderr so we have to wrap it to ensure consistency at least in this
884-
# module
885-
import distutils
886-
887-
class log(object):
888-
def __getattr__(self, attr):
889-
return getattr(distutils.log, attr)
890-
891-
def warn(self, msg, *args):
892-
self._log_to_stderr(distutils.log.WARN, msg, *args)
893-
894-
def error(self, msg):
895-
self._log_to_stderr(distutils.log.ERROR, msg, *args)
896-
897-
def fatal(self, msg):
898-
self._log_to_stderr(distutils.log.FATAL, msg, *args)
899-
900-
def log(self, level, msg, *args):
901-
if level in (distutils.log.WARN, distutils.log.ERROR,
902-
distutils.log.FATAL):
903-
self._log_to_stderr(level, msg, *args)
904-
else:
905-
distutils.log.log(level, msg, *args)
906-
907-
def _log_to_stderr(self, level, msg, *args):
908-
# This is the only truly 'public' way to get the current threshold
909-
# of the log
910-
current_threshold = distutils.log.set_threshold(distutils.log.WARN)
911-
distutils.log.set_threshold(current_threshold)
912-
if level >= current_threshold:
913-
if args:
914-
msg = msg % args
915-
sys.stderr.write('%s\n' % msg)
916-
sys.stderr.flush()
917-
918-
log = log()
919-
920-
921909
BOOTSTRAPPER = _Bootstrapper.main()
922910

923911

0 commit comments

Comments
 (0)