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