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