]>
Commit | Line | Data |
---|---|---|
1 | #!python | |
2 | """Bootstrap distribute installation | |
3 | ||
4 | If you want to use setuptools in your package's setup.py, just include this | |
5 | file in the same directory with it, and add this to the top of your setup.py:: | |
6 | ||
7 | from distribute_setup import use_setuptools | |
8 | use_setuptools() | |
9 | ||
10 | If you want to require a specific version of setuptools, set a download | |
11 | mirror, or use an alternate download directory, you can do so by supplying | |
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 time | |
20 | import fnmatch | |
21 | import tempfile | |
22 | import tarfile | |
23 | import optparse | |
24 | ||
25 | from distutils import log | |
26 | ||
27 | try: | |
28 | from site import USER_SITE | |
29 | except ImportError: | |
30 | USER_SITE = None | |
31 | ||
32 | try: | |
33 | import subprocess | |
34 | ||
35 | def _python_cmd(*args): | |
36 | args = (sys.executable,) + args | |
37 | return subprocess.call(args) == 0 | |
38 | ||
39 | except ImportError: | |
40 | # will be used for python 2.3 | |
41 | def _python_cmd(*args): | |
42 | args = (sys.executable,) + args | |
43 | # quoting arguments if windows | |
44 | if sys.platform == 'win32': | |
45 | def quote(arg): | |
46 | if ' ' in arg: | |
47 | return '"%s"' % arg | |
48 | return arg | |
49 | args = [quote(arg) for arg in args] | |
50 | return os.spawnl(os.P_WAIT, sys.executable, *args) == 0 | |
51 | ||
52 | DEFAULT_VERSION = "0.6.36" | |
53 | DEFAULT_URL = "http://pypi.python.org/packages/source/d/distribute/" | |
54 | SETUPTOOLS_FAKED_VERSION = "0.6c11" | |
55 | ||
56 | SETUPTOOLS_PKG_INFO = """\ | |
57 | Metadata-Version: 1.0 | |
58 | Name: setuptools | |
59 | Version: %s | |
60 | Summary: xxxx | |
61 | Home-page: xxx | |
62 | Author: xxx | |
63 | Author-email: xxx | |
64 | License: xxx | |
65 | Description: xxx | |
66 | """ % SETUPTOOLS_FAKED_VERSION | |
67 | ||
68 | ||
69 | def _install(tarball, install_args=()): | |
70 | # extracting the tarball | |
71 | tmpdir = tempfile.mkdtemp() | |
72 | log.warn('Extracting in %s', tmpdir) | |
73 | old_wd = os.getcwd() | |
74 | try: | |
75 | os.chdir(tmpdir) | |
76 | tar = tarfile.open(tarball) | |
77 | _extractall(tar) | |
78 | tar.close() | |
79 | ||
80 | # going in the directory | |
81 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) | |
82 | os.chdir(subdir) | |
83 | log.warn('Now working in %s', subdir) | |
84 | ||
85 | # installing | |
86 | log.warn('Installing Distribute') | |
87 | if not _python_cmd('setup.py', 'install', *install_args): | |
88 | log.warn('Something went wrong during the installation.') | |
89 | log.warn('See the error message above.') | |
90 | # exitcode will be 2 | |
91 | return 2 | |
92 | finally: | |
93 | os.chdir(old_wd) | |
94 | shutil.rmtree(tmpdir) | |
95 | ||
96 | ||
97 | def _build_egg(egg, tarball, to_dir): | |
98 | # extracting the tarball | |
99 | tmpdir = tempfile.mkdtemp() | |
100 | log.warn('Extracting in %s', tmpdir) | |
101 | old_wd = os.getcwd() | |
102 | try: | |
103 | os.chdir(tmpdir) | |
104 | tar = tarfile.open(tarball) | |
105 | _extractall(tar) | |
106 | tar.close() | |
107 | ||
108 | # going in the directory | |
109 | subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0]) | |
110 | os.chdir(subdir) | |
111 | log.warn('Now working in %s', subdir) | |
112 | ||
113 | # building an egg | |
114 | log.warn('Building a Distribute egg in %s', to_dir) | |
115 | _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir) | |
116 | ||
117 | finally: | |
118 | os.chdir(old_wd) | |
119 | shutil.rmtree(tmpdir) | |
120 | # returning the result | |
121 | log.warn(egg) | |
122 | if not os.path.exists(egg): | |
123 | raise IOError('Could not build the egg.') | |
124 | ||
125 | ||
126 | def _do_download(version, download_base, to_dir, download_delay): | |
127 | egg = os.path.join(to_dir, 'distribute-%s-py%d.%d.egg' | |
128 | % (version, sys.version_info[0], sys.version_info[1])) | |
129 | if not os.path.exists(egg): | |
130 | tarball = download_setuptools(version, download_base, | |
131 | to_dir, download_delay) | |
132 | _build_egg(egg, tarball, to_dir) | |
133 | sys.path.insert(0, egg) | |
134 | import setuptools | |
135 | setuptools.bootstrap_install_from = egg | |
136 | ||
137 | ||
138 | def use_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, | |
139 | to_dir=os.curdir, download_delay=15, no_fake=True): | |
140 | # making sure we use the absolute path | |
141 | to_dir = os.path.abspath(to_dir) | |
142 | was_imported = 'pkg_resources' in sys.modules or \ | |
143 | 'setuptools' in sys.modules | |
144 | try: | |
145 | try: | |
146 | import pkg_resources | |
147 | if not hasattr(pkg_resources, '_distribute'): | |
148 | if not no_fake: | |
149 | _fake_setuptools() | |
150 | raise ImportError | |
151 | except ImportError: | |
152 | return _do_download(version, download_base, to_dir, download_delay) | |
153 | try: | |
154 | pkg_resources.require("distribute>=" + version) | |
155 | return | |
156 | except pkg_resources.VersionConflict: | |
157 | e = sys.exc_info()[1] | |
158 | if was_imported: | |
159 | sys.stderr.write( | |
160 | "The required version of distribute (>=%s) is not available,\n" | |
161 | "and can't be installed while this script is running. Please\n" | |
162 | "install a more recent version first, using\n" | |
163 | "'easy_install -U distribute'." | |
164 | "\n\n(Currently using %r)\n" % (version, e.args[0])) | |
165 | sys.exit(2) | |
166 | else: | |
167 | del pkg_resources, sys.modules['pkg_resources'] # reload ok | |
168 | return _do_download(version, download_base, to_dir, | |
169 | download_delay) | |
170 | except pkg_resources.DistributionNotFound: | |
171 | return _do_download(version, download_base, to_dir, | |
172 | download_delay) | |
173 | finally: | |
174 | if not no_fake: | |
175 | _create_fake_setuptools_pkg_info(to_dir) | |
176 | ||
177 | ||
178 | def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL, | |
179 | to_dir=os.curdir, delay=15): | |
180 | """Download distribute from a specified location and return its filename | |
181 | ||
182 | `version` should be a valid distribute version number that is available | |
183 | as an egg for download under the `download_base` URL (which should end | |
184 | with a '/'). `to_dir` is the directory where the egg will be downloaded. | |
185 | `delay` is the number of seconds to pause before an actual download | |
186 | attempt. | |
187 | """ | |
188 | # making sure we use the absolute path | |
189 | to_dir = os.path.abspath(to_dir) | |
190 | try: | |
191 | from urllib.request import urlopen | |
192 | except ImportError: | |
193 | from urllib2 import urlopen | |
194 | tgz_name = "distribute-%s.tar.gz" % version | |
195 | url = download_base + tgz_name | |
196 | saveto = os.path.join(to_dir, tgz_name) | |
197 | src = dst = None | |
198 | if not os.path.exists(saveto): # Avoid repeated downloads | |
199 | try: | |
200 | log.warn("Downloading %s", url) | |
201 | src = urlopen(url) | |
202 | # Read/write all in one block, so we don't create a corrupt file | |
203 | # if the download is interrupted. | |
204 | data = src.read() | |
205 | dst = open(saveto, "wb") | |
206 | dst.write(data) | |
207 | finally: | |
208 | if src: | |
209 | src.close() | |
210 | if dst: | |
211 | dst.close() | |
212 | return os.path.realpath(saveto) | |
213 | ||
214 | ||
215 | def _no_sandbox(function): | |
216 | def __no_sandbox(*args, **kw): | |
217 | try: | |
218 | from setuptools.sandbox import DirectorySandbox | |
219 | if not hasattr(DirectorySandbox, '_old'): | |
220 | def violation(*args): | |
221 | pass | |
222 | DirectorySandbox._old = DirectorySandbox._violation | |
223 | DirectorySandbox._violation = violation | |
224 | patched = True | |
225 | else: | |
226 | patched = False | |
227 | except ImportError: | |
228 | patched = False | |
229 | ||
230 | try: | |
231 | return function(*args, **kw) | |
232 | finally: | |
233 | if patched: | |
234 | DirectorySandbox._violation = DirectorySandbox._old | |
235 | del DirectorySandbox._old | |
236 | ||
237 | return __no_sandbox | |
238 | ||
239 | ||
240 | def _patch_file(path, content): | |
241 | """Will backup the file then patch it""" | |
242 | f = open(path) | |
243 | existing_content = f.read() | |
244 | f.close() | |
245 | if existing_content == content: | |
246 | # already patched | |
247 | log.warn('Already patched.') | |
248 | return False | |
249 | log.warn('Patching...') | |
250 | _rename_path(path) | |
251 | f = open(path, 'w') | |
252 | try: | |
253 | f.write(content) | |
254 | finally: | |
255 | f.close() | |
256 | return True | |
257 | ||
258 | _patch_file = _no_sandbox(_patch_file) | |
259 | ||
260 | ||
261 | def _same_content(path, content): | |
262 | f = open(path) | |
263 | existing_content = f.read() | |
264 | f.close() | |
265 | return existing_content == content | |
266 | ||
267 | ||
268 | def _rename_path(path): | |
269 | new_name = path + '.OLD.%s' % time.time() | |
270 | log.warn('Renaming %s to %s', path, new_name) | |
271 | os.rename(path, new_name) | |
272 | return new_name | |
273 | ||
274 | ||
275 | def _remove_flat_installation(placeholder): | |
276 | if not os.path.isdir(placeholder): | |
277 | log.warn('Unkown installation at %s', placeholder) | |
278 | return False | |
279 | found = False | |
280 | for file in os.listdir(placeholder): | |
281 | if fnmatch.fnmatch(file, 'setuptools*.egg-info'): | |
282 | found = True | |
283 | break | |
284 | if not found: | |
285 | log.warn('Could not locate setuptools*.egg-info') | |
286 | return | |
287 | ||
288 | log.warn('Moving elements out of the way...') | |
289 | pkg_info = os.path.join(placeholder, file) | |
290 | if os.path.isdir(pkg_info): | |
291 | patched = _patch_egg_dir(pkg_info) | |
292 | else: | |
293 | patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO) | |
294 | ||
295 | if not patched: | |
296 | log.warn('%s already patched.', pkg_info) | |
297 | return False | |
298 | # now let's move the files out of the way | |
299 | for element in ('setuptools', 'pkg_resources.py', 'site.py'): | |
300 | element = os.path.join(placeholder, element) | |
301 | if os.path.exists(element): | |
302 | _rename_path(element) | |
303 | else: | |
304 | log.warn('Could not find the %s element of the ' | |
305 | 'Setuptools distribution', element) | |
306 | return True | |
307 | ||
308 | _remove_flat_installation = _no_sandbox(_remove_flat_installation) | |
309 | ||
310 | ||
311 | def _after_install(dist): | |
312 | log.warn('After install bootstrap.') | |
313 | placeholder = dist.get_command_obj('install').install_purelib | |
314 | _create_fake_setuptools_pkg_info(placeholder) | |
315 | ||
316 | ||
317 | def _create_fake_setuptools_pkg_info(placeholder): | |
318 | if not placeholder or not os.path.exists(placeholder): | |
319 | log.warn('Could not find the install location') | |
320 | return | |
321 | pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1]) | |
322 | setuptools_file = 'setuptools-%s-py%s.egg-info' % \ | |
323 | (SETUPTOOLS_FAKED_VERSION, pyver) | |
324 | pkg_info = os.path.join(placeholder, setuptools_file) | |
325 | if os.path.exists(pkg_info): | |
326 | log.warn('%s already exists', pkg_info) | |
327 | return | |
328 | ||
329 | log.warn('Creating %s', pkg_info) | |
330 | try: | |
331 | f = open(pkg_info, 'w') | |
332 | except EnvironmentError: | |
333 | log.warn("Don't have permissions to write %s, skipping", pkg_info) | |
334 | return | |
335 | try: | |
336 | f.write(SETUPTOOLS_PKG_INFO) | |
337 | finally: | |
338 | f.close() | |
339 | ||
340 | pth_file = os.path.join(placeholder, 'setuptools.pth') | |
341 | log.warn('Creating %s', pth_file) | |
342 | f = open(pth_file, 'w') | |
343 | try: | |
344 | f.write(os.path.join(os.curdir, setuptools_file)) | |
345 | finally: | |
346 | f.close() | |
347 | ||
348 | _create_fake_setuptools_pkg_info = _no_sandbox( | |
349 | _create_fake_setuptools_pkg_info | |
350 | ) | |
351 | ||
352 | ||
353 | def _patch_egg_dir(path): | |
354 | # let's check if it's already patched | |
355 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') | |
356 | if os.path.exists(pkg_info): | |
357 | if _same_content(pkg_info, SETUPTOOLS_PKG_INFO): | |
358 | log.warn('%s already patched.', pkg_info) | |
359 | return False | |
360 | _rename_path(path) | |
361 | os.mkdir(path) | |
362 | os.mkdir(os.path.join(path, 'EGG-INFO')) | |
363 | pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO') | |
364 | f = open(pkg_info, 'w') | |
365 | try: | |
366 | f.write(SETUPTOOLS_PKG_INFO) | |
367 | finally: | |
368 | f.close() | |
369 | return True | |
370 | ||
371 | _patch_egg_dir = _no_sandbox(_patch_egg_dir) | |
372 | ||
373 | ||
374 | def _before_install(): | |
375 | log.warn('Before install bootstrap.') | |
376 | _fake_setuptools() | |
377 | ||
378 | ||
379 | def _under_prefix(location): | |
380 | if 'install' not in sys.argv: | |
381 | return True | |
382 | args = sys.argv[sys.argv.index('install') + 1:] | |
383 | for index, arg in enumerate(args): | |
384 | for option in ('--root', '--prefix'): | |
385 | if arg.startswith('%s=' % option): | |
386 | top_dir = arg.split('root=')[-1] | |
387 | return location.startswith(top_dir) | |
388 | elif arg == option: | |
389 | if len(args) > index: | |
390 | top_dir = args[index + 1] | |
391 | return location.startswith(top_dir) | |
392 | if arg == '--user' and USER_SITE is not None: | |
393 | return location.startswith(USER_SITE) | |
394 | return True | |
395 | ||
396 | ||
397 | def _fake_setuptools(): | |
398 | log.warn('Scanning installed packages') | |
399 | try: | |
400 | import pkg_resources | |
401 | except ImportError: | |
402 | # we're cool | |
403 | log.warn('Setuptools or Distribute does not seem to be installed.') | |
404 | return | |
405 | ws = pkg_resources.working_set | |
406 | try: | |
407 | setuptools_dist = ws.find( | |
408 | pkg_resources.Requirement.parse('setuptools', replacement=False) | |
409 | ) | |
410 | except TypeError: | |
411 | # old distribute API | |
412 | setuptools_dist = ws.find( | |
413 | pkg_resources.Requirement.parse('setuptools') | |
414 | ) | |
415 | ||
416 | if setuptools_dist is None: | |
417 | log.warn('No setuptools distribution found') | |
418 | return | |
419 | # detecting if it was already faked | |
420 | setuptools_location = setuptools_dist.location | |
421 | log.warn('Setuptools installation detected at %s', setuptools_location) | |
422 | ||
423 | # if --root or --preix was provided, and if | |
424 | # setuptools is not located in them, we don't patch it | |
425 | if not _under_prefix(setuptools_location): | |
426 | log.warn('Not patching, --root or --prefix is installing Distribute' | |
427 | ' in another location') | |
428 | return | |
429 | ||
430 | # let's see if its an egg | |
431 | if not setuptools_location.endswith('.egg'): | |
432 | log.warn('Non-egg installation') | |
433 | res = _remove_flat_installation(setuptools_location) | |
434 | if not res: | |
435 | return | |
436 | else: | |
437 | log.warn('Egg installation') | |
438 | pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO') | |
439 | if (os.path.exists(pkg_info) and | |
440 | _same_content(pkg_info, SETUPTOOLS_PKG_INFO)): | |
441 | log.warn('Already patched.') | |
442 | return | |
443 | log.warn('Patching...') | |
444 | # let's create a fake egg replacing setuptools one | |
445 | res = _patch_egg_dir(setuptools_location) | |
446 | if not res: | |
447 | return | |
448 | log.warn('Patching complete.') | |
449 | _relaunch() | |
450 | ||
451 | ||
452 | def _relaunch(): | |
453 | log.warn('Relaunching...') | |
454 | # we have to relaunch the process | |
455 | # pip marker to avoid a relaunch bug | |
456 | _cmd1 = ['-c', 'install', '--single-version-externally-managed'] | |
457 | _cmd2 = ['-c', 'install', '--record'] | |
458 | if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2: | |
459 | sys.argv[0] = 'setup.py' | |
460 | args = [sys.executable] + sys.argv | |
461 | sys.exit(subprocess.call(args)) | |
462 | ||
463 | ||
464 | def _extractall(self, path=".", members=None): | |
465 | """Extract all members from the archive to the current working | |
466 | directory and set owner, modification time and permissions on | |
467 | directories afterwards. `path' specifies a different directory | |
468 | to extract to. `members' is optional and must be a subset of the | |
469 | list returned by getmembers(). | |
470 | """ | |
471 | import copy | |
472 | import operator | |
473 | from tarfile import ExtractError | |
474 | directories = [] | |
475 | ||
476 | if members is None: | |
477 | members = self | |
478 | ||
479 | for tarinfo in members: | |
480 | if tarinfo.isdir(): | |
481 | # Extract directories with a safe mode. | |
482 | directories.append(tarinfo) | |
483 | tarinfo = copy.copy(tarinfo) | |
484 | tarinfo.mode = 448 # decimal for oct 0700 | |
485 | self.extract(tarinfo, path) | |
486 | ||
487 | # Reverse sort directories. | |
488 | if sys.version_info < (2, 4): | |
489 | def sorter(dir1, dir2): | |
490 | return cmp(dir1.name, dir2.name) | |
491 | directories.sort(sorter) | |
492 | directories.reverse() | |
493 | else: | |
494 | directories.sort(key=operator.attrgetter('name'), reverse=True) | |
495 | ||
496 | # Set correct owner, mtime and filemode on directories. | |
497 | for tarinfo in directories: | |
498 | dirpath = os.path.join(path, tarinfo.name) | |
499 | try: | |
500 | self.chown(tarinfo, dirpath) | |
501 | self.utime(tarinfo, dirpath) | |
502 | self.chmod(tarinfo, dirpath) | |
503 | except ExtractError: | |
504 | e = sys.exc_info()[1] | |
505 | if self.errorlevel > 1: | |
506 | raise | |
507 | else: | |
508 | self._dbg(1, "tarfile: %s" % e) | |
509 | ||
510 | ||
511 | def _build_install_args(options): | |
512 | """ | |
513 | Build the arguments to 'python setup.py install' on the distribute package | |
514 | """ | |
515 | install_args = [] | |
516 | if options.user_install: | |
517 | if sys.version_info < (2, 6): | |
518 | log.warn("--user requires Python 2.6 or later") | |
519 | raise SystemExit(1) | |
520 | install_args.append('--user') | |
521 | return install_args | |
522 | ||
523 | def _parse_args(): | |
524 | """ | |
525 | Parse the command line for options | |
526 | """ | |
527 | parser = optparse.OptionParser() | |
528 | parser.add_option( | |
529 | '--user', dest='user_install', action='store_true', default=False, | |
530 | help='install in user site package (requires Python 2.6 or later)') | |
531 | parser.add_option( | |
532 | '--download-base', dest='download_base', metavar="URL", | |
533 | default=DEFAULT_URL, | |
534 | help='alternative URL from where to download the distribute package') | |
535 | options, args = parser.parse_args() | |
536 | # positional arguments are ignored | |
537 | return options | |
538 | ||
539 | def main(version=DEFAULT_VERSION): | |
540 | """Install or upgrade setuptools and EasyInstall""" | |
541 | options = _parse_args() | |
542 | tarball = download_setuptools(download_base=options.download_base) | |
543 | return _install(tarball, _build_install_args(options)) | |
544 | ||
545 | if __name__ == '__main__': | |
546 | sys.exit(main()) |