distribute_setup.py 15 KB

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