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 site import USER_SITE |
---|
31 | except ImportError: |
---|
32 | USER_SITE = None |
---|
33 | |
---|
34 | DEFAULT_VERSION = "3.4.1" |
---|
35 | DEFAULT_URL = "https://pypi.python.org/packages/source/s/setuptools/" |
---|
36 | |
---|
37 | def _python_cmd(*args): |
---|
38 | """ |
---|
39 | Return True if the command succeeded. |
---|
40 | """ |
---|
41 | args = (sys.executable,) + args |
---|
42 | return subprocess.call(args) == 0 |
---|
43 | |
---|
44 | |
---|
45 | def _install(archive_filename, install_args=()): |
---|
46 | with archive_context(archive_filename): |
---|
47 | # installing |
---|
48 | log.warn('Installing Setuptools') |
---|
49 | if not _python_cmd('setup.py', 'install', *install_args): |
---|
50 | log.warn('Something went wrong during the installation.') |
---|
51 | log.warn('See the error message above.') |
---|
52 | # exitcode will be 2 |
---|
53 | return 2 |
---|
54 | |
---|
55 | |
---|
56 | def _build_egg(egg, archive_filename, to_dir): |
---|
57 | with archive_context(archive_filename): |
---|
58 | # building an egg |
---|
59 | log.warn('Building a Setuptools egg in %s', to_dir) |
---|
60 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) |
---|
61 | # returning the result |
---|
62 | log.warn(egg) |
---|
63 | if not os.path.exists(egg): |
---|
64 | raise IOError('Could not build the egg.') |
---|
65 | |
---|
66 | |
---|
67 | def get_zip_class(): |
---|
68 | """ |
---|
69 | Supplement ZipFile class to support context manager for Python 2.6 |
---|
70 | """ |
---|
71 | class ContextualZipFile(zipfile.ZipFile): |
---|
72 | def __enter__(self): |
---|
73 | return self |
---|
74 | def __exit__(self, type, value, traceback): |
---|
75 | self.close |
---|
76 | return zipfile.ZipFile if hasattr(zipfile.ZipFile, '__exit__') else \ |
---|
77 | ContextualZipFile |
---|
78 | |
---|
79 | |
---|
80 | @contextlib.contextmanager |
---|
81 | def archive_context(filename): |
---|
82 | # extracting the archive |
---|
83 | tmpdir = tempfile.mkdtemp() |
---|
84 | log.warn('Extracting in %s', tmpdir) |
---|
85 | old_wd = os.getcwd() |
---|
86 | try: |
---|
87 | os.chdir(tmpdir) |
---|
88 | with get_zip_class()(filename) as archive: |
---|
89 | archive.extractall() |
---|
90 | |
---|
91 | # going in the directory |
---|
92 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) |
---|
93 | os.chdir(subdir) |
---|
94 | log.warn('Now working in %s', subdir) |
---|
95 | yield |
---|
96 | |
---|
97 | finally: |
---|
98 | os.chdir(old_wd) |
---|
99 | shutil.rmtree(tmpdir) |
---|
100 | |
---|
101 | |
---|
102 | def _do_download(version, download_base, to_dir, download_delay): |
---|
103 | egg = os.path.join(to_dir, 'setuptools-%s-py%d.%d.egg' |
---|
104 | % (version, sys.version_info[0], sys.version_info[1])) |
---|
105 | if not os.path.exists(egg): |
---|
106 | archive = download_setuptools(version, download_base, |
---|
107 | to_dir, download_delay) |
---|
108 | _build_egg(egg, archive, to_dir) |
---|
109 | sys.path.insert(0, egg) |
---|
110 | |
---|
111 | # Remove previously-imported pkg_resources if present (see |
---|
112 | # https://bitbucket.org/pypa/setuptools/pull-request/7/ for details). |
---|
113 | if 'pkg_resources' in sys.modules: |
---|
114 | del sys.modules['pkg_resources'] |
---|
115 | |
---|
116 | import setuptools |
---|
117 | setuptools.bootstrap_install_from = egg |
---|
118 | |
---|
119 | |
---|
120 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, |
---|
121 | to_dir=os.curdir, download_delay=15): |
---|
122 | to_dir = os.path.abspath(to_dir) |
---|
123 | rep_modules = 'pkg_resources', 'setuptools' |
---|
124 | imported = set(sys.modules).intersection(rep_modules) |
---|
125 | try: |
---|
126 | import pkg_resources |
---|
127 | except ImportError: |
---|
128 | return _do_download(version, download_base, to_dir, download_delay) |
---|
129 | try: |
---|
130 | pkg_resources.require("setuptools>=" + version) |
---|
131 | return |
---|
132 | except pkg_resources.DistributionNotFound: |
---|
133 | return _do_download(version, download_base, to_dir, download_delay) |
---|
134 | except pkg_resources.VersionConflict as VC_err: |
---|
135 | if imported: |
---|
136 | msg = textwrap.dedent(""" |
---|
137 | The required version of setuptools (>={version}) is not available, |
---|
138 | and can't be installed while this script is running. Please |
---|
139 | install a more recent version first, using |
---|
140 | 'easy_install -U setuptools'. |
---|
141 | |
---|
142 | (Currently using {VC_err.args[0]!r}) |
---|
143 | """).format(VC_err=VC_err, version=version) |
---|
144 | sys.stderr.write(msg) |
---|
145 | sys.exit(2) |
---|
146 | |
---|
147 | # otherwise, reload ok |
---|
148 | del pkg_resources, sys.modules['pkg_resources'] |
---|
149 | return _do_download(version, download_base, to_dir, download_delay) |
---|
150 | |
---|
151 | def _clean_check(cmd, target): |
---|
152 | """ |
---|
153 | Run the command to download target. If the command fails, clean up before |
---|
154 | re-raising the error. |
---|
155 | """ |
---|
156 | try: |
---|
157 | subprocess.check_call(cmd) |
---|
158 | except subprocess.CalledProcessError: |
---|
159 | if os.access(target, os.F_OK): |
---|
160 | os.unlink(target) |
---|
161 | raise |
---|
162 | |
---|
163 | def download_file_powershell(url, target): |
---|
164 | """ |
---|
165 | Download the file at url to target using Powershell (which will validate |
---|
166 | trust). Raise an exception if the command cannot complete. |
---|
167 | """ |
---|
168 | target = os.path.abspath(target) |
---|
169 | cmd = [ |
---|
170 | 'powershell', |
---|
171 | '-Command', |
---|
172 | "(new-object System.Net.WebClient).DownloadFile(%(url)r, %(target)r)" % vars(), |
---|
173 | ] |
---|
174 | _clean_check(cmd, target) |
---|
175 | |
---|
176 | def has_powershell(): |
---|
177 | if platform.system() != 'Windows': |
---|
178 | return False |
---|
179 | cmd = ['powershell', '-Command', 'echo test'] |
---|
180 | devnull = open(os.path.devnull, 'wb') |
---|
181 | try: |
---|
182 | try: |
---|
183 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull) |
---|
184 | except Exception: |
---|
185 | return False |
---|
186 | finally: |
---|
187 | devnull.close() |
---|
188 | return True |
---|
189 | |
---|
190 | download_file_powershell.viable = has_powershell |
---|
191 | |
---|
192 | def download_file_curl(url, target): |
---|
193 | cmd = ['curl', url, '--silent', '--output', target] |
---|
194 | _clean_check(cmd, target) |
---|
195 | |
---|
196 | def has_curl(): |
---|
197 | cmd = ['curl', '--version'] |
---|
198 | devnull = open(os.path.devnull, 'wb') |
---|
199 | try: |
---|
200 | try: |
---|
201 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull) |
---|
202 | except Exception: |
---|
203 | return False |
---|
204 | finally: |
---|
205 | devnull.close() |
---|
206 | return True |
---|
207 | |
---|
208 | download_file_curl.viable = has_curl |
---|
209 | |
---|
210 | def download_file_wget(url, target): |
---|
211 | cmd = ['wget', url, '--quiet', '--output-document', target] |
---|
212 | _clean_check(cmd, target) |
---|
213 | |
---|
214 | def has_wget(): |
---|
215 | cmd = ['wget', '--version'] |
---|
216 | devnull = open(os.path.devnull, 'wb') |
---|
217 | try: |
---|
218 | try: |
---|
219 | subprocess.check_call(cmd, stdout=devnull, stderr=devnull) |
---|
220 | except Exception: |
---|
221 | return False |
---|
222 | finally: |
---|
223 | devnull.close() |
---|
224 | return True |
---|
225 | |
---|
226 | download_file_wget.viable = has_wget |
---|
227 | |
---|
228 | def download_file_insecure(url, target): |
---|
229 | """ |
---|
230 | Use Python to download the file, even though it cannot authenticate the |
---|
231 | connection. |
---|
232 | """ |
---|
233 | try: |
---|
234 | from urllib.request import urlopen |
---|
235 | except ImportError: |
---|
236 | from urllib2 import urlopen |
---|
237 | src = dst = None |
---|
238 | try: |
---|
239 | src = urlopen(url) |
---|
240 | # Read/write all in one block, so we don't create a corrupt file |
---|
241 | # if the download is interrupted. |
---|
242 | data = src.read() |
---|
243 | dst = open(target, "wb") |
---|
244 | dst.write(data) |
---|
245 | finally: |
---|
246 | if src: |
---|
247 | src.close() |
---|
248 | if dst: |
---|
249 | dst.close() |
---|
250 | |
---|
251 | download_file_insecure.viable = lambda: True |
---|
252 | |
---|
253 | def get_best_downloader(): |
---|
254 | downloaders = [ |
---|
255 | download_file_powershell, |
---|
256 | download_file_curl, |
---|
257 | download_file_wget, |
---|
258 | download_file_insecure, |
---|
259 | ] |
---|
260 | |
---|
261 | for dl in downloaders: |
---|
262 | if dl.viable(): |
---|
263 | return dl |
---|
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 egg 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()) |
---|