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