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