distribute_setup.py

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

mercurial