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: 188 return False 189 finally: 190 devnull.close() 191 return True 192 193download_file_powershell.viable = has_powershell 194 195def download_file_curl(url, target): 196 cmd = ['curl', url, '--silent', '--output', target] 197 _clean_check(cmd, target) 198 199def has_curl(): 200 cmd = ['curl', '--version'] 201 devnull = open(os.path.devnull, 'wb') 202 try: 203 try: 204 subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 205 except: 206 return False 207 finally: 208 devnull.close() 209 return True 210 211download_file_curl.viable = has_curl 212 213def download_file_wget(url, target): 214 cmd = ['wget', url, '--quiet', '--output-document', target] 215 _clean_check(cmd, target) 216 217def has_wget(): 218 cmd = ['wget', '--version'] 219 devnull = open(os.path.devnull, 'wb') 220 try: 221 try: 222 subprocess.check_call(cmd, stdout=devnull, stderr=devnull) 223 except: 224 return False 225 finally: 226 devnull.close() 227 return True 228 229download_file_wget.viable = has_wget 230 231def download_file_insecure(url, target): 232 """ 233 Use Python to download the file, even though it cannot authenticate the 234 connection. 235 """ 236 try: 237 from urllib.request import urlopen 238 except ImportError: 239 from urllib2 import urlopen 240 src = dst = None 241 try: 242 src = urlopen(url) 243 # Read/write all in one block, so we don't create a corrupt file 244 # if the download is interrupted. 245 data = src.read() 246 dst = open(target, "wb") 247 dst.write(data) 248 finally: 249 if src: 250 src.close() 251 if dst: 252 dst.close() 253 254download_file_insecure.viable = lambda: True 255 256def get_best_downloader(): 257 downloaders = [ 258 download_file_powershell, 259 download_file_curl, 260 download_file_wget, 261 download_file_insecure, 262 ] 263 264 for dl in downloaders: 265 if dl.viable(): 266 return dl 267 268def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, 269 to_dir=os.curdir, delay=15, 270 downloader_factory=get_best_downloader): 271 """Download setuptools from a specified location and return its filename 272 273 `version` should be a valid setuptools version number that is available 274 as an egg for download under the `download_base` URL (which should end 275 with a '/'). `to_dir` is the directory where the egg will be downloaded. 276 `delay` is the number of seconds to pause before an actual download 277 attempt. 278 279 ``downloader_factory`` should be a function taking no arguments and 280 returning a function for downloading a URL to a target. 281 """ 282 # making sure we use the absolute path 283 to_dir = os.path.abspath(to_dir) 284 tgz_name = "setuptools-%s.tar.gz" % version 285 url = download_base + tgz_name 286 saveto = os.path.join(to_dir, tgz_name) 287 if not os.path.exists(saveto): # Avoid repeated downloads 288 log.warn("Downloading %s", url) 289 downloader = downloader_factory() 290 downloader(url, saveto) 291 return os.path.realpath(saveto) 292 293 294def _extractall(self, path=".", members=None): 295 """Extract all members from the archive to the current working 296 directory and set owner, modification time and permissions on 297 directories afterwards. `path' specifies a different directory 298 to extract to. `members' is optional and must be a subset of the 299 list returned by getmembers(). 300 """ 301 import copy 302 import operator 303 from tarfile import ExtractError 304 directories = [] 305 306 if members is None: 307 members = self 308 309 for tarinfo in members: 310 if tarinfo.isdir(): 311 # Extract directories with a safe mode. 312 directories.append(tarinfo) 313 tarinfo = copy.copy(tarinfo) 314 tarinfo.mode = 448 # decimal for oct 0700 315 self.extract(tarinfo, path) 316 317 # Reverse sort directories. 318 if sys.version_info < (2, 4): 319 def sorter(dir1, dir2): 320 return cmp(dir1.name, dir2.name) 321 directories.sort(sorter) 322 directories.reverse() 323 else: 324 directories.sort(key=operator.attrgetter('name'), reverse=True) 325 326 # Set correct owner, mtime and filemode on directories. 327 for tarinfo in directories: 328 dirpath = os.path.join(path, tarinfo.name) 329 try: 330 self.chown(tarinfo, dirpath) 331 self.utime(tarinfo, dirpath) 332 self.chmod(tarinfo, dirpath) 333 except ExtractError: 334 e = sys.exc_info()[1] 335 if self.errorlevel > 1: 336 raise 337 else: 338 self._dbg(1, "tarfile: %s" % e) 339 340 341def _build_install_args(options): 342 """ 343 Build the arguments to 'python setup.py install' on the setuptools package 344 """ 345 install_args = [] 346 if options.user_install: 347 if sys.version_info < (2, 6): 348 log.warn("--user requires Python 2.6 or later") 349 raise SystemExit(1) 350 install_args.append('--user') 351 return install_args 352 353def _parse_args(): 354 """ 355 Parse the command line for options 356 """ 357 parser = optparse.OptionParser() 358 parser.add_option( 359 '--user', dest='user_install', action='store_true', default=False, 360 help='install in user site package (requires Python 2.6 or later)') 361 parser.add_option( 362 '--download-base', dest='download_base', metavar="URL", 363 default=DEFAULT_URL, 364 help='alternative URL from where to download the setuptools package') 365 parser.add_option( 366 '--insecure', dest='downloader_factory', action='store_const', 367 const=lambda: download_file_insecure, default=get_best_downloader, 368 help='Use internal, non-validating downloader' 369 ) 370 options, args = parser.parse_args() 371 # positional arguments are ignored 372 return options 373 374def main(version=DEFAULT_VERSION): 375 """Install or upgrade setuptools and EasyInstall""" 376 options = _parse_args() 377 tarball = download_setuptools(download_base=options.download_base, 378 downloader_factory=options.downloader_factory) 379 return _install(tarball, _build_install_args(options)) 380 381if __name__ == '__main__': 382 sys.exit(main()) 383