source: sasview/ez_setup.py @ 22e6043

Last change on this file since 22e6043 was 64aa49e, checked in by Mathieu Doucet <doucetm@…>, 11 years ago

Update setuptools

  • Property mode set to 100644
File size: 10.1 KB
Line 
1#!/usr/bin/env python
2"""Bootstrap setuptools installation
3
4To use setuptools in your package's setup.py, include this
5file in the same directory and add this to the top of your setup.py::
6
7    from ez_setup import use_setuptools
8    use_setuptools()
9
10To require a specific version of setuptools, set a download
11mirror, or use an alternate download directory, simply supply
12the appropriate options to ``use_setuptools()``.
13
14This file can also be run as a script to install or upgrade setuptools.
15"""
16import os
17import shutil
18import sys
19import tempfile
20import zipfile
21import optparse
22import subprocess
23import platform
24import textwrap
25import contextlib
26
27from distutils import log
28
29try:
30    from site import USER_SITE
31except ImportError:
32    USER_SITE = None
33
34DEFAULT_VERSION = "3.4.1"
35DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
36
37def _python_cmd(*args):
38    """
39    Return True if the command succeeded.
40    """
41    args = (sys.executable,) + args
42    return subprocess.call(args) == 0
43
44
45def _install(archive_filename, install_args=()):
46    with archive_context(archive_filename):
47        # installing
48        log.warn('Installing Setuptools')
49        if not _python_cmd('setup.py', 'install', *install_args):
50            log.warn('Something went wrong during the installation.')
51            log.warn('See the error message above.')
52            # exitcode will be 2
53            return 2
54
55
56def _build_egg(egg, archive_filename, to_dir):
57    with archive_context(archive_filename):
58        # building an egg
59        log.warn('Building a Setuptools egg in %s', to_dir)
60        _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
61    # returning the result
62    log.warn(egg)
63    if not os.path.exists(egg):
64        raise IOError('Could not build the egg.')
65
66
67def get_zip_class():
68    """
69    Supplement ZipFile class to support context manager for Python 2.6
70    """
71    class ContextualZipFile(zipfile.ZipFile):
72        def __enter__(self):
73            return self
74        def __exit__(self, type, value, traceback):
75            self.close
76    return zipfile.ZipFile if hasattr(zipfile.ZipFile, '__exit__') else \
77        ContextualZipFile
78
79
80@contextlib.contextmanager
81def archive_context(filename):
82    # extracting the archive
83    tmpdir = tempfile.mkdtemp()
84    log.warn('Extracting in %s', tmpdir)
85    old_wd = os.getcwd()
86    try:
87        os.chdir(tmpdir)
88        with get_zip_class()(filename) as archive:
89            archive.extractall()
90
91        # going in the directory
92        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
93        os.chdir(subdir)
94        log.warn('Now working in %s', subdir)
95        yield
96
97    finally:
98        os.chdir(old_wd)
99        shutil.rmtree(tmpdir)
100
101
102def _do_download(version, download_base, to_dir, download_delay):
103    egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg'
104                       % (version, sys.version_info[0], sys.version_info[1]))
105    if not os.path.exists(egg):
106        archive = download_setuptools(version, download_base,
107                                      to_dir, download_delay)
108        _build_egg(egg, archive, to_dir)
109    sys.path.insert(0, egg)
110
111    # Remove previously-imported pkg_resources if present (see
112    # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details).
113    if 'pkg_resources' in sys.modules:
114        del sys.modules['pkg_resources']
115
116    import setuptools
117    setuptools.bootstrap_install_from = egg
118
119
120def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
121        to_dir=os.curdir, download_delay=15):
122    to_dir = os.path.abspath(to_dir)
123    rep_modules = 'pkg_resources', 'setuptools'
124    imported = set(sys.modules).intersection(rep_modules)
125    try:
126        import pkg_resources
127    except ImportError:
128        return _do_download(version, download_base, to_dir, download_delay)
129    try:
130        pkg_resources.require("setuptools>=" + version)
131        return
132    except pkg_resources.DistributionNotFound:
133        return _do_download(version, download_base, to_dir, download_delay)
134    except pkg_resources.VersionConflict as VC_err:
135        if imported:
136            msg = textwrap.dedent("""
137                The required version of setuptools (>={version}) is not available,
138                and can't be installed while this script is running. Please
139                install a more recent version first, using
140                'easy_install -U setuptools'.
141
142                (Currently using {VC_err.args[0]!r})
143                """).format(VC_err=VC_err, version=version)
144            sys.stderr.write(msg)
145            sys.exit(2)
146
147        # otherwise, reload ok
148        del pkg_resources, sys.modules['pkg_resources']
149        return _do_download(version, download_base, to_dir, download_delay)
150
151def _clean_check(cmd, target):
152    """
153    Run the command to download target. If the command fails, clean up before
154    re-raising the error.
155    """
156    try:
157        subprocess.check_call(cmd)
158    except subprocess.CalledProcessError:
159        if os.access(target, os.F_OK):
160            os.unlink(target)
161        raise
162
163def download_file_powershell(url, target):
164    """
165    Download the file at url to target using Powershell (which will validate
166    trust). Raise an exception if the command cannot complete.
167    """
168    target = os.path.abspath(target)
169    cmd = [
170        'powershell',
171        '-Command',
172        "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(),
173    ]
174    _clean_check(cmd, target)
175
176def has_powershell():
177    if platform.system() != 'Windows':
178        return False
179    cmd = ['powershell', '-Command', 'echo test']
180    devnull = open(os.path.devnull, 'wb')
181    try:
182        try:
183            subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
184        except Exception:
185            return False
186    finally:
187        devnull.close()
188    return True
189
190download_file_powershell.viable = has_powershell
191
192def download_file_curl(url, target):
193    cmd = ['curl', url, '--silent', '--output', target]
194    _clean_check(cmd, target)
195
196def has_curl():
197    cmd = ['curl', '--version']
198    devnull = open(os.path.devnull, 'wb')
199    try:
200        try:
201            subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
202        except Exception:
203            return False
204    finally:
205        devnull.close()
206    return True
207
208download_file_curl.viable = has_curl
209
210def download_file_wget(url, target):
211    cmd = ['wget', url, '--quiet', '--output-document', target]
212    _clean_check(cmd, target)
213
214def has_wget():
215    cmd = ['wget', '--version']
216    devnull = open(os.path.devnull, 'wb')
217    try:
218        try:
219            subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
220        except Exception:
221            return False
222    finally:
223        devnull.close()
224    return True
225
226download_file_wget.viable = has_wget
227
228def download_file_insecure(url, target):
229    """
230    Use Python to download the file, even though it cannot authenticate the
231    connection.
232    """
233    try:
234        from urllib.request import urlopen
235    except ImportError:
236        from urllib2 import urlopen
237    src = dst = None
238    try:
239        src = urlopen(url)
240        # Read/write all in one block, so we don't create a corrupt file
241        # if the download is interrupted.
242        data = src.read()
243        dst = open(target, "wb")
244        dst.write(data)
245    finally:
246        if src:
247            src.close()
248        if dst:
249            dst.close()
250
251download_file_insecure.viable = lambda: True
252
253def get_best_downloader():
254    downloaders = [
255        download_file_powershell,
256        download_file_curl,
257        download_file_wget,
258        download_file_insecure,
259    ]
260
261    for dl in downloaders:
262        if dl.viable():
263            return dl
264
265def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
266        to_dir=os.curdir, delay=15, downloader_factory=get_best_downloader):
267    """
268    Download setuptools from a specified location and return its filename
269
270    `version` should be a valid setuptools version number that is available
271    as an egg for download under the `download_base` URL (which should end
272    with a '/'). `to_dir` is the directory where the egg will be downloaded.
273    `delay` is the number of seconds to pause before an actual download
274    attempt.
275
276    ``downloader_factory`` should be a function taking no arguments and
277    returning a function for downloading a URL to a target.
278    """
279    # making sure we use the absolute path
280    to_dir = os.path.abspath(to_dir)
281    zip_name = "setuptools-%s.zip" % version
282    url = download_base + zip_name
283    saveto = os.path.join(to_dir, zip_name)
284    if not os.path.exists(saveto):  # Avoid repeated downloads
285        log.warn("Downloading %s", url)
286        downloader = downloader_factory()
287        downloader(url, saveto)
288    return os.path.realpath(saveto)
289
290def _build_install_args(options):
291    """
292    Build the arguments to 'python setup.py install' on the setuptools package
293    """
294    return ['--user'] if options.user_install else []
295
296def _parse_args():
297    """
298    Parse the command line for options
299    """
300    parser = optparse.OptionParser()
301    parser.add_option(
302        '--user', dest='user_install', action='store_true', default=False,
303        help='install in user site package (requires Python 2.6 or later)')
304    parser.add_option(
305        '--download-base', dest='download_base', metavar="URL",
306        default=DEFAULT_URL,
307        help='alternative URL from where to download the setuptools package')
308    parser.add_option(
309        '--insecure', dest='downloader_factory', action='store_const',
310        const=lambda: download_file_insecure, default=get_best_downloader,
311        help='Use internal, non-validating downloader'
312    )
313    parser.add_option(
314        '--version', help="Specify which version to download",
315        default=DEFAULT_VERSION,
316    )
317    options, args = parser.parse_args()
318    # positional arguments are ignored
319    return options
320
321def main():
322    """Install or upgrade setuptools and EasyInstall"""
323    options = _parse_args()
324    archive = download_setuptools(
325        version=options.version,
326        download_base=options.download_base,
327        downloader_factory=options.downloader_factory,
328    )
329    return _install(archive, _build_install_args(options))
330
331if __name__ == '__main__':
332    sys.exit(main())
Note: See TracBrowser for help on using the repository browser.