|
20 | 20 | import tarfile
|
21 | 21 | import optparse
|
22 | 22 | import subprocess
|
| 23 | +import platform |
23 | 24 |
|
24 | 25 | from distutils import log
|
25 | 26 |
|
|
28 | 29 | except ImportError:
|
29 | 30 | USER_SITE = None
|
30 | 31 |
|
31 |
| -DEFAULT_VERSION = "0.9.8" |
| 32 | +DEFAULT_VERSION = "1.4.2" |
32 | 33 | DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/"
|
33 | 34 |
|
34 | 35 | def _python_cmd(*args):
|
35 | 36 | args = (sys.executable,) + args
|
36 | 37 | return subprocess.call(args) == 0
|
37 | 38 |
|
| 39 | +def _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) |
| 46 | +vars(subprocess).setdefault('check_call', _check_call_py24) |
| 47 | + |
38 | 48 | def _install(tarball, install_args=()):
|
39 | 49 | # extracting the tarball
|
40 | 50 | tmpdir = tempfile.mkdtemp()
|
@@ -141,41 +151,143 @@ def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
141 | 151 | return _do_download(version, download_base, to_dir,
|
142 | 152 | download_delay)
|
143 | 153 |
|
| 154 | +def _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 | + |
| 166 | +def 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 | + |
| 179 | +def 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 | + |
| 193 | +download_file_powershell.viable = has_powershell |
| 194 | + |
| 195 | +def download_file_curl(url, target): |
| 196 | + cmd = ['curl', url, '--silent', '--output', target] |
| 197 | + _clean_check(cmd, target) |
| 198 | + |
| 199 | +def 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 | + |
| 211 | +download_file_curl.viable = has_curl |
| 212 | + |
| 213 | +def download_file_wget(url, target): |
| 214 | + cmd = ['wget', url, '--quiet', '--output-document', target] |
| 215 | + _clean_check(cmd, target) |
| 216 | + |
| 217 | +def 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 | + |
| 229 | +download_file_wget.viable = has_wget |
| 230 | + |
| 231 | +def 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 | + |
| 254 | +download_file_insecure.viable = lambda: True |
| 255 | + |
| 256 | +def 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 |
144 | 267 |
|
145 | 268 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
|
146 |
| - to_dir=os.curdir, delay=15): |
| 269 | + to_dir=os.curdir, delay=15, |
| 270 | + downloader_factory=get_best_downloader): |
147 | 271 | """Download setuptools from a specified location and return its filename
|
148 | 272 |
|
149 | 273 | `version` should be a valid setuptools version number that is available
|
150 | 274 | as an egg for download under the `download_base` URL (which should end
|
151 | 275 | with a '/'). `to_dir` is the directory where the egg will be downloaded.
|
152 | 276 | `delay` is the number of seconds to pause before an actual download
|
153 | 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. |
154 | 281 | """
|
155 | 282 | # making sure we use the absolute path
|
156 | 283 | to_dir = os.path.abspath(to_dir)
|
157 |
| - try: |
158 |
| - from urllib.request import urlopen |
159 |
| - except ImportError: |
160 |
| - from urllib2 import urlopen |
161 | 284 | tgz_name = "setuptools-%s.tar.gz" % version
|
162 | 285 | url = download_base + tgz_name
|
163 | 286 | saveto = os.path.join(to_dir, tgz_name)
|
164 |
| - src = dst = None |
165 | 287 | if not os.path.exists(saveto): # Avoid repeated downloads
|
166 |
| - try: |
167 |
| - log.warn("Downloading %s", url) |
168 |
| - src = urlopen(url) |
169 |
| - # Read/write all in one block, so we don't create a corrupt file |
170 |
| - # if the download is interrupted. |
171 |
| - data = src.read() |
172 |
| - dst = open(saveto, "wb") |
173 |
| - dst.write(data) |
174 |
| - finally: |
175 |
| - if src: |
176 |
| - src.close() |
177 |
| - if dst: |
178 |
| - dst.close() |
| 288 | + log.warn("Downloading %s", url) |
| 289 | + downloader = downloader_factory() |
| 290 | + downloader(url, saveto) |
179 | 291 | return os.path.realpath(saveto)
|
180 | 292 |
|
181 | 293 |
|
@@ -250,14 +362,20 @@ def _parse_args():
|
250 | 362 | '--download-base', dest='download_base', metavar="URL",
|
251 | 363 | default=DEFAULT_URL,
|
252 | 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 | + ) |
253 | 370 | options, args = parser.parse_args()
|
254 | 371 | # positional arguments are ignored
|
255 | 372 | return options
|
256 | 373 |
|
257 | 374 | def main(version=DEFAULT_VERSION):
|
258 | 375 | """Install or upgrade setuptools and EasyInstall"""
|
259 | 376 | options = _parse_args()
|
260 |
| - tarball = download_setuptools(download_base=options.download_base) |
| 377 | + tarball = download_setuptools(download_base=options.download_base, |
| 378 | + downloader_factory=options.downloader_factory) |
261 | 379 | return _install(tarball, _build_install_args(options))
|
262 | 380 |
|
263 | 381 | if __name__ == '__main__':
|
|
0 commit comments