comparison ez_setup.py @ 83:12b5b1435e17

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