SvrInstall: ez_setup.py

File ez_setup.py, 12.5 KB (added by admin, 9 years ago)
Line 
1#!/usr/bin/env python
2
3"""
4Setuptools bootstrapping installer.
5
6Maintained at https://github.com/pypa/setuptools/tree/bootstrap.
7
8Run this script to install or upgrade setuptools.
9"""
10
11import os
12import shutil
13import sys
14import tempfile
15import zipfile
16import optparse
17import subprocess
18import platform
19import textwrap
20import contextlib
21import json
22import codecs
23
24from distutils import log
25
26try:
27    from urllib.request import urlopen
28    from urllib.parse import urljoin
29except ImportError:
30    from urllib2 import urlopen
31    from urlparse import urljoin
32
33try:
34    from site import USER_SITE
35except ImportError:
36    USER_SITE = None
37
38LATEST = object()
39DEFAULT_VERSION = LATEST
40DEFAULT_URL = "https://pypi.io/packages/source/s/setuptools/"
41DEFAULT_SAVE_DIR = os.curdir
42
43
44def _python_cmd(*args):
45    """
46    Execute a command.
47
48    Return True if the command succeeded.
49    """
50    args = (sys.executable,) + args
51    return subprocess.call(args) == 0
52
53
54def _install(archive_filename, install_args=()):
55    """Install Setuptools."""
56    with archive_context(archive_filename):
57        # installing
58        log.warn('Installing Setuptools')
59        if not _python_cmd('setup.py', 'install', *install_args):
60            log.warn('Something went wrong during the installation.')
61            log.warn('See the error message above.')
62            # exitcode will be 2
63            return 2
64
65
66def _build_egg(egg, archive_filename, to_dir):
67    """Build Setuptools egg."""
68    with archive_context(archive_filename):
69        # building an egg
70        log.warn('Building a Setuptools egg in %s', to_dir)
71        _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
72    # returning the result
73    log.warn(egg)
74    if not os.path.exists(egg):
75        raise IOError('Could not build the egg.')
76
77
78class ContextualZipFile(zipfile.ZipFile):
79
80    """Supplement ZipFile class to support context manager for Python 2.6."""
81
82    def __enter__(self):
83        return self
84
85    def __exit__(self, type, value, traceback):
86        self.close()
87
88    def __new__(cls, *args, **kwargs):
89        """Construct a ZipFile or ContextualZipFile as appropriate."""
90        if hasattr(zipfile.ZipFile, '__exit__'):
91            return zipfile.ZipFile(*args, **kwargs)
92        return super(ContextualZipFile, cls).__new__(cls)
93
94
95@contextlib.contextmanager
96def archive_context(filename):
97    """
98    Unzip filename to a temporary directory, set to the cwd.
99
100    The unzipped target is cleaned up after.
101    """
102    tmpdir = tempfile.mkdtemp()
103    log.warn('Extracting in %s', tmpdir)
104    old_wd = os.getcwd()
105    try:
106        os.chdir(tmpdir)
107        with ContextualZipFile(filename) as archive:
108            archive.extractall()
109
110        # going in the directory
111        subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
112        os.chdir(subdir)
113        log.warn('Now working in %s', subdir)
114        yield
115
116    finally:
117        os.chdir(old_wd)
118        shutil.rmtree(tmpdir)
119
120
121def _do_download(version, download_base, to_dir, download_delay):
122    """Download Setuptools."""
123    egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg'
124                       % (version, sys.version_info[0], sys.version_info[1]))
125    if not os.path.exists(egg):
126        archive = download_setuptools(version, download_base,
127                                      to_dir, download_delay)
128        _build_egg(egg, archive, to_dir)
129    sys.path.insert(0, egg)
130
131    # Remove previously-imported pkg_resources if present (see
132    # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details).
133    if 'pkg_resources' in sys.modules:
134        _unload_pkg_resources()
135
136    import setuptools
137    setuptools.bootstrap_install_from = egg
138
139
140def use_setuptools(
141        version=DEFAULT_VERSION, download_base=DEFAULT_URL,
142        to_dir=DEFAULT_SAVE_DIR, download_delay=15):
143    """
144    Ensure that a setuptools version is installed.
145
146    Return None. Raise SystemExit if the requested version
147    or later cannot be installed.
148    """
149    version = _resolve_version(version)
150    to_dir = os.path.abspath(to_dir)
151
152    # prior to importing, capture the module state for
153    # representative modules.
154    rep_modules = 'pkg_resources', 'setuptools'
155    imported = set(sys.modules).intersection(rep_modules)
156
157    try:
158        import pkg_resources
159        pkg_resources.require("setuptools>=" + version)
160        # a suitable version is already installed
161        return
162    except ImportError:
163        # pkg_resources not available; setuptools is not installed; download
164        pass
165    except pkg_resources.DistributionNotFound:
166        # no version of setuptools was found; allow download
167        pass
168    except pkg_resources.VersionConflict as VC_err:
169        if imported:
170            _conflict_bail(VC_err, version)
171
172        # otherwise, unload pkg_resources to allow the downloaded version to
173        #  take precedence.
174        del pkg_resources
175        _unload_pkg_resources()
176
177    return _do_download(version, download_base, to_dir, download_delay)
178
179
180def _conflict_bail(VC_err, version):
181    """
182    Setuptools was imported prior to invocation, so it is
183    unsafe to unload it. Bail out.
184    """
185    conflict_tmpl = textwrap.dedent("""
186        The required version of setuptools (>={version}) is not available,
187        and can't be installed while this script is running. Please
188        install a more recent version first, using
189        'easy_install -U setuptools'.
190
191        (Currently using {VC_err.args[0]!r})
192        """)
193    msg = conflict_tmpl.format(**locals())
194    sys.stderr.write(msg)
195    sys.exit(2)
196
197
198def _unload_pkg_resources():
199    sys.meta_path = [
200        importer
201        for importer in sys.meta_path
202        if importer.__class__.__module__ != 'pkg_resources.extern'
203    ]
204    del_modules = [
205        name for name in sys.modules
206        if name.startswith('pkg_resources')
207    ]
208    for mod_name in del_modules:
209        del sys.modules[mod_name]
210
211
212def _clean_check(cmd, target):
213    """
214    Run the command to download target.
215
216    If the command fails, clean up before re-raising the error.
217    """
218    try:
219        subprocess.check_call(cmd)
220    except subprocess.CalledProcessError:
221        if os.access(target, os.F_OK):
222            os.unlink(target)
223        raise
224
225
226def download_file_powershell(url, target):
227    """
228    Download the file at url to target using Powershell.
229
230    Powershell will validate trust.
231    Raise an exception if the command cannot complete.
232    """
233    target = os.path.abspath(target)
234    ps_cmd = (
235        "[System.Net.WebRequest]::DefaultWebProxy.Credentials = "
236        "[System.Net.CredentialCache]::DefaultCredentials; "
237        '(new-object System.Net.WebClient).DownloadFile("%(url)s", "%(target)s")'
238        % locals()
239    )
240    cmd = [
241        'powershell',
242        '-Command',
243        ps_cmd,
244    ]
245    _clean_check(cmd, target)
246
247
248def has_powershell():
249    """Determine if Powershell is available."""
250    if platform.system() != 'Windows':
251        return False
252    cmd = ['powershell', '-Command', 'echo test']
253    with open(os.path.devnull, 'wb') as devnull:
254        try:
255            subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
256        except Exception:
257            return False
258    return True
259download_file_powershell.viable = has_powershell
260
261
262def download_file_curl(url, target):
263    cmd = ['curl', url, '--location', '--silent', '--output', target]
264    _clean_check(cmd, target)
265
266
267def has_curl():
268    cmd = ['curl', '--version']
269    with open(os.path.devnull, 'wb') as devnull:
270        try:
271            subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
272        except Exception:
273            return False
274    return True
275download_file_curl.viable = has_curl
276
277
278def download_file_wget(url, target):
279    cmd = ['wget', url, '--quiet', '--output-document', target]
280    _clean_check(cmd, target)
281
282
283def has_wget():
284    cmd = ['wget', '--version']
285    with open(os.path.devnull, 'wb') as devnull:
286        try:
287            subprocess.check_call(cmd, stdout=devnull, stderr=devnull)
288        except Exception:
289            return False
290    return True
291download_file_wget.viable = has_wget
292
293
294def download_file_insecure(url, target):
295    """Use Python to download the file, without connection authentication."""
296    src = urlopen(url)
297    try:
298        # Read all the data in one block.
299        data = src.read()
300    finally:
301        src.close()
302
303    # Write all the data in one block to avoid creating a partial file.
304    with open(target, "wb") as dst:
305        dst.write(data)
306download_file_insecure.viable = lambda: True
307
308
309def get_best_downloader():
310    downloaders = (
311        download_file_powershell,
312        download_file_curl,
313        download_file_wget,
314        download_file_insecure,
315    )
316    viable_downloaders = (dl for dl in downloaders if dl.viable())
317    return next(viable_downloaders, None)
318
319
320def download_setuptools(
321        version=DEFAULT_VERSION, download_base=DEFAULT_URL,
322        to_dir=DEFAULT_SAVE_DIR, delay=15,
323        downloader_factory=get_best_downloader):
324    """
325    Download setuptools from a specified location and return its filename.
326
327    `version` should be a valid setuptools version number that is available
328    as an sdist for download under the `download_base` URL (which should end
329    with a '/'). `to_dir` is the directory where the egg will be downloaded.
330    `delay` is the number of seconds to pause before an actual download
331    attempt.
332
333    ``downloader_factory`` should be a function taking no arguments and
334    returning a function for downloading a URL to a target.
335    """
336    version = _resolve_version(version)
337    # making sure we use the absolute path
338    to_dir = os.path.abspath(to_dir)
339    zip_name = "setuptools-%s.zip" % version
340    url = download_base + zip_name
341    saveto = os.path.join(to_dir, zip_name)
342    if not os.path.exists(saveto):  # Avoid repeated downloads
343        log.warn("Downloading %s", url)
344        downloader = downloader_factory()
345        downloader(url, saveto)
346    return os.path.realpath(saveto)
347
348
349def _resolve_version(version):
350    """
351    Resolve LATEST version
352    """
353    if version is not LATEST:
354        return version
355
356    meta_url = urljoin(DEFAULT_URL, '/pypi/setuptools/json')
357    resp = urlopen(meta_url)
358    with contextlib.closing(resp):
359        try:
360            charset = resp.info().get_content_charset()
361        except Exception:
362            # Python 2 compat; assume UTF-8
363            charset = 'UTF-8'
364        reader = codecs.getreader(charset)
365        doc = json.load(reader(resp))
366
367    return str(doc['info']['version'])
368
369
370def _build_install_args(options):
371    """
372    Build the arguments to 'python setup.py install' on the setuptools package.
373
374    Returns list of command line arguments.
375    """
376    return ['--user'] if options.user_install else []
377
378
379def _parse_args():
380    """Parse the command line for options."""
381    parser = optparse.OptionParser()
382    parser.add_option(
383        '--user', dest='user_install', action='store_true', default=False,
384        help='install in user site package')
385    parser.add_option(
386        '--download-base', dest='download_base', metavar="URL",
387        default=DEFAULT_URL,
388        help='alternative URL from where to download the setuptools package')
389    parser.add_option(
390        '--insecure', dest='downloader_factory', action='store_const',
391        const=lambda: download_file_insecure, default=get_best_downloader,
392        help='Use internal, non-validating downloader'
393    )
394    parser.add_option(
395        '--version', help="Specify which version to download",
396        default=DEFAULT_VERSION,
397    )
398    parser.add_option(
399        '--to-dir',
400        help="Directory to save (and re-use) package",
401        default=DEFAULT_SAVE_DIR,
402    )
403    options, args = parser.parse_args()
404    # positional arguments are ignored
405    return options
406
407
408def _download_args(options):
409    """Return args for download_setuptools function from cmdline args."""
410    return dict(
411        version=options.version,
412        download_base=options.download_base,
413        downloader_factory=options.downloader_factory,
414        to_dir=options.to_dir,
415    )
416
417
418def main():
419    """Install or upgrade setuptools and EasyInstall."""
420    options = _parse_args()
421    archive = download_setuptools(**_download_args(options))
422    return _install(archive, _build_install_args(options))
423
424if __name__ == '__main__':
425    sys.exit(main())