comparison distribute_setup.py @ 360:9834136b15ed

added setup.py for the installation with setuptools
author souliane <souliane@mailoo.org>
date Tue, 18 Feb 2014 00:15:43 +0100
parents
children
comparison
equal deleted inserted replaced
359:151bf1afd97e 360:9834136b15ed
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.49"
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
148 # Setuptools 0.7b and later is a suitable (and preferable)
149 # substitute for any Distribute version.
150 try:
151 pkg_resources.require("setuptools>=0.7b")
152 return
153 except (pkg_resources.DistributionNotFound,
154 pkg_resources.VersionConflict):
155 pass
156
157 if not hasattr(pkg_resources, '_distribute'):
158 if not no_fake:
159 _fake_setuptools()
160 raise ImportError
161 except ImportError:
162 return _do_download(version, download_base, to_dir, download_delay)
163 try:
164 pkg_resources.require("distribute>=" + version)
165 return
166 except pkg_resources.VersionConflict:
167 e = sys.exc_info()[1]
168 if was_imported:
169 sys.stderr.write(
170 "The required version of distribute (>=%s) is not available,\n"
171 "and can't be installed while this script is running. Please\n"
172 "install a more recent version first, using\n"
173 "'easy_install -U distribute'."
174 "\n\n(Currently using %r)\n" % (version, e.args[0]))
175 sys.exit(2)
176 else:
177 del pkg_resources, sys.modules['pkg_resources'] # reload ok
178 return _do_download(version, download_base, to_dir,
179 download_delay)
180 except pkg_resources.DistributionNotFound:
181 return _do_download(version, download_base, to_dir,
182 download_delay)
183 finally:
184 if not no_fake:
185 _create_fake_setuptools_pkg_info(to_dir)
186
187
188 def download_setuptools(version=DEFAULT_VERSION, download_base=DEFAULT_URL,
189 to_dir=os.curdir, delay=15):
190 """Download distribute from a specified location and return its filename
191
192 `version` should be a valid distribute version number that is available
193 as an egg for download under the `download_base` URL (which should end
194 with a '/'). `to_dir` is the directory where the egg will be downloaded.
195 `delay` is the number of seconds to pause before an actual download
196 attempt.
197 """
198 # making sure we use the absolute path
199 to_dir = os.path.abspath(to_dir)
200 try:
201 from urllib.request import urlopen
202 except ImportError:
203 from urllib2 import urlopen
204 tgz_name = "distribute-%s.tar.gz" % version
205 url = download_base + tgz_name
206 saveto = os.path.join(to_dir, tgz_name)
207 src = dst = None
208 if not os.path.exists(saveto): # Avoid repeated downloads
209 try:
210 log.warn("Downloading %s", url)
211 src = urlopen(url)
212 # Read/write all in one block, so we don't create a corrupt file
213 # if the download is interrupted.
214 data = src.read()
215 dst = open(saveto, "wb")
216 dst.write(data)
217 finally:
218 if src:
219 src.close()
220 if dst:
221 dst.close()
222 return os.path.realpath(saveto)
223
224
225 def _no_sandbox(function):
226 def __no_sandbox(*args, **kw):
227 try:
228 from setuptools.sandbox import DirectorySandbox
229 if not hasattr(DirectorySandbox, '_old'):
230 def violation(*args):
231 pass
232 DirectorySandbox._old = DirectorySandbox._violation
233 DirectorySandbox._violation = violation
234 patched = True
235 else:
236 patched = False
237 except ImportError:
238 patched = False
239
240 try:
241 return function(*args, **kw)
242 finally:
243 if patched:
244 DirectorySandbox._violation = DirectorySandbox._old
245 del DirectorySandbox._old
246
247 return __no_sandbox
248
249
250 def _patch_file(path, content):
251 """Will backup the file then patch it"""
252 f = open(path)
253 existing_content = f.read()
254 f.close()
255 if existing_content == content:
256 # already patched
257 log.warn('Already patched.')
258 return False
259 log.warn('Patching...')
260 _rename_path(path)
261 f = open(path, 'w')
262 try:
263 f.write(content)
264 finally:
265 f.close()
266 return True
267
268 _patch_file = _no_sandbox(_patch_file)
269
270
271 def _same_content(path, content):
272 f = open(path)
273 existing_content = f.read()
274 f.close()
275 return existing_content == content
276
277
278 def _rename_path(path):
279 new_name = path + '.OLD.%s' % time.time()
280 log.warn('Renaming %s to %s', path, new_name)
281 os.rename(path, new_name)
282 return new_name
283
284
285 def _remove_flat_installation(placeholder):
286 if not os.path.isdir(placeholder):
287 log.warn('Unkown installation at %s', placeholder)
288 return False
289 found = False
290 for file in os.listdir(placeholder):
291 if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
292 found = True
293 break
294 if not found:
295 log.warn('Could not locate setuptools*.egg-info')
296 return
297
298 log.warn('Moving elements out of the way...')
299 pkg_info = os.path.join(placeholder, file)
300 if os.path.isdir(pkg_info):
301 patched = _patch_egg_dir(pkg_info)
302 else:
303 patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
304
305 if not patched:
306 log.warn('%s already patched.', pkg_info)
307 return False
308 # now let's move the files out of the way
309 for element in ('setuptools', 'pkg_resources.py', 'site.py'):
310 element = os.path.join(placeholder, element)
311 if os.path.exists(element):
312 _rename_path(element)
313 else:
314 log.warn('Could not find the %s element of the '
315 'Setuptools distribution', element)
316 return True
317
318 _remove_flat_installation = _no_sandbox(_remove_flat_installation)
319
320
321 def _after_install(dist):
322 log.warn('After install bootstrap.')
323 placeholder = dist.get_command_obj('install').install_purelib
324 _create_fake_setuptools_pkg_info(placeholder)
325
326
327 def _create_fake_setuptools_pkg_info(placeholder):
328 if not placeholder or not os.path.exists(placeholder):
329 log.warn('Could not find the install location')
330 return
331 pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
332 setuptools_file = 'setuptools-%s-py%s.egg-info' % \
333 (SETUPTOOLS_FAKED_VERSION, pyver)
334 pkg_info = os.path.join(placeholder, setuptools_file)
335 if os.path.exists(pkg_info):
336 log.warn('%s already exists', pkg_info)
337 return
338
339 log.warn('Creating %s', pkg_info)
340 try:
341 f = open(pkg_info, 'w')
342 except EnvironmentError:
343 log.warn("Don't have permissions to write %s, skipping", pkg_info)
344 return
345 try:
346 f.write(SETUPTOOLS_PKG_INFO)
347 finally:
348 f.close()
349
350 pth_file = os.path.join(placeholder, 'setuptools.pth')
351 log.warn('Creating %s', pth_file)
352 f = open(pth_file, 'w')
353 try:
354 f.write(os.path.join(os.curdir, setuptools_file))
355 finally:
356 f.close()
357
358 _create_fake_setuptools_pkg_info = _no_sandbox(
359 _create_fake_setuptools_pkg_info
360 )
361
362
363 def _patch_egg_dir(path):
364 # let's check if it's already patched
365 pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
366 if os.path.exists(pkg_info):
367 if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
368 log.warn('%s already patched.', pkg_info)
369 return False
370 _rename_path(path)
371 os.mkdir(path)
372 os.mkdir(os.path.join(path, 'EGG-INFO'))
373 pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
374 f = open(pkg_info, 'w')
375 try:
376 f.write(SETUPTOOLS_PKG_INFO)
377 finally:
378 f.close()
379 return True
380
381 _patch_egg_dir = _no_sandbox(_patch_egg_dir)
382
383
384 def _before_install():
385 log.warn('Before install bootstrap.')
386 _fake_setuptools()
387
388
389 def _under_prefix(location):
390 if 'install' not in sys.argv:
391 return True
392 args = sys.argv[sys.argv.index('install') + 1:]
393 for index, arg in enumerate(args):
394 for option in ('--root', '--prefix'):
395 if arg.startswith('%s=' % option):
396 top_dir = arg.split('root=')[-1]
397 return location.startswith(top_dir)
398 elif arg == option:
399 if len(args) > index:
400 top_dir = args[index + 1]
401 return location.startswith(top_dir)
402 if arg == '--user' and USER_SITE is not None:
403 return location.startswith(USER_SITE)
404 return True
405
406
407 def _fake_setuptools():
408 log.warn('Scanning installed packages')
409 try:
410 import pkg_resources
411 except ImportError:
412 # we're cool
413 log.warn('Setuptools or Distribute does not seem to be installed.')
414 return
415 ws = pkg_resources.working_set
416 try:
417 setuptools_dist = ws.find(
418 pkg_resources.Requirement.parse('setuptools', replacement=False)
419 )
420 except TypeError:
421 # old distribute API
422 setuptools_dist = ws.find(
423 pkg_resources.Requirement.parse('setuptools')
424 )
425
426 if setuptools_dist is None:
427 log.warn('No setuptools distribution found')
428 return
429 # detecting if it was already faked
430 setuptools_location = setuptools_dist.location
431 log.warn('Setuptools installation detected at %s', setuptools_location)
432
433 # if --root or --preix was provided, and if
434 # setuptools is not located in them, we don't patch it
435 if not _under_prefix(setuptools_location):
436 log.warn('Not patching, --root or --prefix is installing Distribute'
437 ' in another location')
438 return
439
440 # let's see if its an egg
441 if not setuptools_location.endswith('.egg'):
442 log.warn('Non-egg installation')
443 res = _remove_flat_installation(setuptools_location)
444 if not res:
445 return
446 else:
447 log.warn('Egg installation')
448 pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
449 if (os.path.exists(pkg_info) and
450 _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
451 log.warn('Already patched.')
452 return
453 log.warn('Patching...')
454 # let's create a fake egg replacing setuptools one
455 res = _patch_egg_dir(setuptools_location)
456 if not res:
457 return
458 log.warn('Patching complete.')
459 _relaunch()
460
461
462 def _relaunch():
463 log.warn('Relaunching...')
464 # we have to relaunch the process
465 # pip marker to avoid a relaunch bug
466 _cmd1 = ['-c', 'install', '--single-version-externally-managed']
467 _cmd2 = ['-c', 'install', '--record']
468 if sys.argv[:3] == _cmd1 or sys.argv[:3] == _cmd2:
469 sys.argv[0] = 'setup.py'
470 args = [sys.executable] + sys.argv
471 sys.exit(subprocess.call(args))
472
473
474 def _extractall(self, path=".", members=None):
475 """Extract all members from the archive to the current working
476 directory and set owner, modification time and permissions on
477 directories afterwards. `path' specifies a different directory
478 to extract to. `members' is optional and must be a subset of the
479 list returned by getmembers().
480 """
481 import copy
482 import operator
483 from tarfile import ExtractError
484 directories = []
485
486 if members is None:
487 members = self
488
489 for tarinfo in members:
490 if tarinfo.isdir():
491 # Extract directories with a safe mode.
492 directories.append(tarinfo)
493 tarinfo = copy.copy(tarinfo)
494 tarinfo.mode = 448 # decimal for oct 0700
495 self.extract(tarinfo, path)
496
497 # Reverse sort directories.
498 if sys.version_info < (2, 4):
499 def sorter(dir1, dir2):
500 return cmp(dir1.name, dir2.name)
501 directories.sort(sorter)
502 directories.reverse()
503 else:
504 directories.sort(key=operator.attrgetter('name'), reverse=True)
505
506 # Set correct owner, mtime and filemode on directories.
507 for tarinfo in directories:
508 dirpath = os.path.join(path, tarinfo.name)
509 try:
510 self.chown(tarinfo, dirpath)
511 self.utime(tarinfo, dirpath)
512 self.chmod(tarinfo, dirpath)
513 except ExtractError:
514 e = sys.exc_info()[1]
515 if self.errorlevel > 1:
516 raise
517 else:
518 self._dbg(1, "tarfile: %s" % e)
519
520
521 def _build_install_args(options):
522 """
523 Build the arguments to 'python setup.py install' on the distribute package
524 """
525 install_args = []
526 if options.user_install:
527 if sys.version_info < (2, 6):
528 log.warn("--user requires Python 2.6 or later")
529 raise SystemExit(1)
530 install_args.append('--user')
531 return install_args
532
533 def _parse_args():
534 """
535 Parse the command line for options
536 """
537 parser = optparse.OptionParser()
538 parser.add_option(
539 '--user', dest='user_install', action='store_true', default=False,
540 help='install in user site package (requires Python 2.6 or later)')
541 parser.add_option(
542 '--download-base', dest='download_base', metavar="URL",
543 default=DEFAULT_URL,
544 help='alternative URL from where to download the distribute package')
545 options, args = parser.parse_args()
546 # positional arguments are ignored
547 return options
548
549 def main(version=DEFAULT_VERSION):
550 """Install or upgrade setuptools and EasyInstall"""
551 options = _parse_args()
552 tarball = download_setuptools(download_base=options.download_base)
553 return _install(tarball, _build_install_args(options))
554
555 if __name__ == '__main__':
556 sys.exit(main())