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