1#!python 2"""Bootstrap setuptools installation 3 4If you want to use setuptools in your package's setup.py, just include this 5file in the same directory with it, and add this to the top of your setup.py:: 6 7 from ez_setup import use_setuptools 8 use_setuptools() 9 10If you want to require a specific version of setuptools, set a download 11mirror, or use an alternate download directory, you can do so by supplying 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 tarfile 21import optparse 22import subprocess 23import platform 24 25from distutils import log 26 27try: 28 from site import USER_SITE 29except ImportError: 30 USER_SITE = None 31 32DEFAULT_VERSION = "1.4.2" 33DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" 34 35def _python_cmd(*args): 36 args = (sys.executable,) + args 37 return subprocess.call(args) == 0 38 39def _check_call_py24(cmd, *args, **kwargs): 40 res = subprocess.call(cmd, *args, **kwargs) 41 class CalledProcessError(Exception): 42 pass 43 if not res == 0: 44 msg = "Command '%s' return non-zero exit status %d" % (cmd, res) 45 raise CalledProcessError(msg) 46vars(subprocess).setdefault('check_call', _check_call_py24) 47 48def _install(tarball, install_args=()): 49 # extracting the tarball 50 tmpdir = tempfile.mkdtemp() 51 log.warn('Extracting in %s', tmpdir) 52 old_wd = os.getcwd() 53 try: 54 os.chdir(tmpdir) 55 tar = tarfile.open(tarball) 56 _extractall(tar) 57 tar.close() 58 59 # going in the directory 60 subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 61 os.chdir(subdir) 62 log.warn('Now working in %s', subdir) 63 64 # installing 65 log.warn('Installing Setuptools') 66 if not _python_cmd('setup.py', 'install', *install_args): 67 log.warn('Something went wrong during the installation.') 68 log.warn('See the error message above.') 69 # exitcode will be 2 70 return 2 71 finally: 72 os.chdir(old_wd) 73 shutil.rmtree(tmpdir) 74 75 76def _build_egg(egg, tarball, to_dir): 77 # extracting the tarball 78 tmpdir = tempfile.mkdtemp() 79 log.warn('Extracting in %s', tmpdir) 80 old_wd = os.getcwd() 81 try: 82 os.chdir(tmpdir) 83 tar = tarfile.open(tarball) 84 _extractall(tar) 85 tar.close() 86 87 # going in the directory 88 subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) 89 os.chdir(subdir) 90 log.warn('Now working in %s', subdir) 91 92 # building an egg 93 log.warn('Building a Setuptools egg in %s', to_dir) 94 _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) 95 96 finally: 97 os.chdir(old_wd) 98 shutil.rmtree(tmpdir) 99 # returning the result 100 log.warn(egg) 101 if not os.path.exists(egg): 102 raise IOError('Could not build the egg.') 103 104 105def _do_download(version, download_base, to_dir, download_delay): 106 egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' 107 % (version, sys.version_info[0], sys.version_info[1])) 108 if not os.path.exists(egg): 109 tarball = download_setuptools(version, download_base, 110 to_dir, download_delay) 111 _build_egg(egg, tarball, to_dir) 112 sys.path.insert(0, egg) 113 114 # Remove previously-imported pkg_resources if present (see 115 # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). 116 if 'pkg_resources' in sys.modules: 117 del sys.modules['pkg_resources'] 118 119 import setuptools 120 setuptools.bootstrap_install_from = egg 121 122 123def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 124 to_dir=os.curdir, download_delay=15): 125 # making sure we use the absolute path 126 to_dir = os.path.abspath(to_dir) 127 was_imported = 'pkg_resources' in sys.modules or \ 128 'setuptools' in sys.modules 129 try: 130 import pkg_resources 131 except ImportError: 132 return _do_download(version, download_base, to_dir, download_delay) 133 try: 134 pkg_resources.require("setuptools>=" + version) 135 return 136 except pkg_resources.VersionConflict: 137 e = sys.exc_info()[1] 138 if was_imported: 139 sys.stderr.write( 140 "The required version of setuptools (>=%s) is not available,\n" 141 "and can't be installed while this script is running. Please\n" 142 "install a more recent version first, using\n" 143 "'easy_install -U setuptools'." 144 "\n\n(Currently using %r)\n" % (version, e.args[0])) 145 sys.exit(2) 146 else: 147 del pkg_resources, sys.modules['pkg_resources'] # reload ok 148 return _do_download(version, download_base, to_dir, 149 download_delay) 150 except pkg_resources.DistributionNotFound: 151 return _do_download(version, download_base, to_dir, 152 download_delay) 153 154def _clean_check(cmd, target): 155 """ 156 Run the command to download target. If the command fails, clean up before 157 re-raising the error. 158 """ 159 try: 160 subprocess.check_call(cmd) 161 except subprocess.CalledProcessError: 162 if os.access(target, os.F_OK): 163 os.unlink(target) 164 raise 165 166def download_file_powershell(url, target): 167 """ 168 Download the file at url to target using Powershell (which will validate 169 trust). Raise an exception if the command cannot complete. 170 """ 171 target = os.path.abspath(target) 172 cmd = [ 173 'powershell', 174 '-Command', 175 "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), 176 ] 177 _clean_check(cmd, target) 178 179def has_powershell(): 180 if platform.system() != 'Windows': 181 return False 182 cmd = ['powershell', '-Command', 'echo test'] 183 devnull = open(os.path.devnull, 'wb') 184 try: 185 try: 186 subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 187 except (KeyboardInterrupt, SystemExit): 188 raise 189 except Exception: 190 return False 191 finally: 192 devnull.close() 193 return True 194 195download_file_powershell.viable = has_powershell 196 197def download_file_curl(url, target): 198 cmd = ['curl', url, '--silent', '--output', target] 199 _clean_check(cmd, target) 200 201def has_curl(): 202 cmd = ['curl', '--version'] 203 devnull = open(os.path.devnull, 'wb') 204 try: 205 try: 206 subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 207 except (KeyboardInterrupt, SystemExit): 208 raise 209 except Exception: 210 return False 211 finally: 212 devnull.close() 213 return True 214 215download_file_curl.viable = has_curl 216 217def download_file_wget(url, target): 218 cmd = ['wget', url, '--quiet', '--output-document', target] 219 _clean_check(cmd, target) 220 221def has_wget(): 222 cmd = ['wget', '--version'] 223 devnull = open(os.path.devnull, 'wb') 224 try: 225 try: 226 subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 227 except (KeyboardInterrupt, SystemExit): 228 raise 229 except Exception: 230 return False 231 finally: 232 devnull.close() 233 return True 234 235download_file_wget.viable = has_wget 236 237def download_file_insecure(url, target): 238 """ 239 Use Python to download the file, even though it cannot authenticate the 240 connection. 241 """ 242 try: 243 from urllib.request import urlopen 244 except ImportError: 245 from urllib2 import urlopen 246 src = dst = None 247 try: 248 src = urlopen(url) 249 # Read/write all in one block, so we don't create a corrupt file 250 # if the download is interrupted. 251 data = src.read() 252 dst = open(target, "wb") 253 dst.write(data) 254 finally: 255 if src: 256 src.close() 257 if dst: 258 dst.close() 259 260download_file_insecure.viable = lambda: True 261 262def get_best_downloader(): 263 downloaders = [ 264 download_file_powershell, 265 download_file_curl, 266 download_file_wget, 267 download_file_insecure, 268 ] 269 270 for dl in downloaders: 271 if dl.viable(): 272 return dl 273 274def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 275 to_dir=os.curdir, delay=15, 276 downloader_factory=get_best_downloader): 277 """Download setuptools from a specified location and return its filename 278 279 `version` should be a valid setuptools version number that is available 280 as an egg for download under the `download_base` URL (which should end 281 with a '/'). `to_dir` is the directory where the egg will be downloaded. 282 `delay` is the number of seconds to pause before an actual download 283 attempt. 284 285 ``downloader_factory`` should be a function taking no arguments and 286 returning a function for downloading a URL to a target. 287 """ 288 # making sure we use the absolute path 289 to_dir = os.path.abspath(to_dir) 290 tgz_name = "setuptools-%s.tar.gz" % version 291 url = download_base + tgz_name 292 saveto = os.path.join(to_dir, tgz_name) 293 if not os.path.exists(saveto): # Avoid repeated downloads 294 log.warn("Downloading %s", url) 295 downloader = downloader_factory() 296 downloader(url, saveto) 297 return os.path.realpath(saveto) 298 299 300def _extractall(self, path=".", members=None): 301 """Extract all members from the archive to the current working 302 directory and set owner, modification time and permissions on 303 directories afterwards. `path' specifies a different directory 304 to extract to. `members' is optional and must be a subset of the 305 list returned by getmembers(). 306 """ 307 import copy 308 import operator 309 from tarfile import ExtractError 310 directories = [] 311 312 if members is None: 313 members = self 314 315 for tarinfo in members: 316 if tarinfo.isdir(): 317 # Extract directories with a safe mode. 318 directories.append(tarinfo) 319 tarinfo = copy.copy(tarinfo) 320 tarinfo.mode = 448 # decimal for oct 0700 321 self.extract(tarinfo, path) 322 323 # Reverse sort directories. 324 if sys.version_info < (2, 4): 325 def sorter(dir1, dir2): 326 return cmp(dir1.name, dir2.name) 327 directories.sort(sorter) 328 directories.reverse() 329 else: 330 directories.sort(key=operator.attrgetter('name'), reverse=True) 331 332 # Set correct owner, mtime and filemode on directories. 333 for tarinfo in directories: 334 dirpath = os.path.join(path, tarinfo.name) 335 try: 336 self.chown(tarinfo, dirpath) 337 self.utime(tarinfo, dirpath) 338 self.chmod(tarinfo, dirpath) 339 except ExtractError: 340 e = sys.exc_info()[1] 341 if self.errorlevel > 1: 342 raise 343 else: 344 self._dbg(1, "tarfile: %s" % e) 345 346 347def _build_install_args(options): 348 """ 349 Build the arguments to 'python setup.py install' on the setuptools package 350 """ 351 install_args = [] 352 if options.user_install: 353 if sys.version_info < (2, 6): 354 log.warn("--user requires Python 2.6 or later") 355 raise SystemExit(1) 356 install_args.append('--user') 357 return install_args 358 359def _parse_args(): 360 """ 361 Parse the command line for options 362 """ 363 parser = optparse.OptionParser() 364 parser.add_option( 365 '--user', dest='user_install', action='store_true', default=False, 366 help='install in user site package (requires Python 2.6 or later)') 367 parser.add_option( 368 '--download-base', dest='download_base', metavar="URL", 369 default=DEFAULT_URL, 370 help='alternative URL from where to download the setuptools package') 371 parser.add_option( 372 '--insecure', dest='downloader_factory', action='store_const', 373 const=lambda: download_file_insecure, default=get_best_downloader, 374 help='Use internal, non-validating downloader' 375 ) 376 options, args = parser.parse_args() 377 # positional arguments are ignored 378 return options 379 380def main(version=DEFAULT_VERSION): 381 """Install or upgrade setuptools and EasyInstall""" 382 options = _parse_args() 383 tarball = download_setuptools(download_base=options.download_base, 384 downloader_factory=options.downloader_factory) 385 return _install(tarball, _build_install_args(options)) 386 387if __name__ == '__main__': 388 sys.exit(main()) 389