distribute_setup.py 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486
  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.14"
  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 _no_sandbox(function):
  181. def __no_sandbox(*args, **kw):
  182. try:
  183. from setuptools.sandbox import DirectorySandbox
  184. if not hasattr(DirectorySandbox, '_old'):
  185. def violation(*args):
  186. pass
  187. DirectorySandbox._old = DirectorySandbox._violation
  188. DirectorySandbox._violation = violation
  189. patched = True
  190. else:
  191. patched = False
  192. except ImportError:
  193. patched = False
  194. try:
  195. return function(*args, **kw)
  196. finally:
  197. if patched:
  198. DirectorySandbox._violation = DirectorySandbox._old
  199. del DirectorySandbox._old
  200. return __no_sandbox
  201. def _patch_file(path, content):
  202. """Will backup the file then patch it"""
  203. existing_content = open(path).read()
  204. if existing_content == content:
  205. # already patched
  206. log.warn('Already patched.')
  207. return False
  208. log.warn('Patching...')
  209. _rename_path(path)
  210. f = open(path, 'w')
  211. try:
  212. f.write(content)
  213. finally:
  214. f.close()
  215. return True
  216. _patch_file = _no_sandbox(_patch_file)
  217. def _same_content(path, content):
  218. return open(path).read() == content
  219. def _rename_path(path):
  220. new_name = path + '.OLD.%s' % time.time()
  221. log.warn('Renaming %s into %s', path, new_name)
  222. os.rename(path, new_name)
  223. return new_name
  224. def _remove_flat_installation(placeholder):
  225. if not os.path.isdir(placeholder):
  226. log.warn('Unkown installation at %s', placeholder)
  227. return False
  228. found = False
  229. for file in os.listdir(placeholder):
  230. if fnmatch.fnmatch(file, 'setuptools*.egg-info'):
  231. found = True
  232. break
  233. if not found:
  234. log.warn('Could not locate setuptools*.egg-info')
  235. return
  236. log.warn('Removing elements out of the way...')
  237. pkg_info = os.path.join(placeholder, file)
  238. if os.path.isdir(pkg_info):
  239. patched = _patch_egg_dir(pkg_info)
  240. else:
  241. patched = _patch_file(pkg_info, SETUPTOOLS_PKG_INFO)
  242. if not patched:
  243. log.warn('%s already patched.', pkg_info)
  244. return False
  245. # now let's move the files out of the way
  246. for element in ('setuptools', 'pkg_resources.py', 'site.py'):
  247. element = os.path.join(placeholder, element)
  248. if os.path.exists(element):
  249. _rename_path(element)
  250. else:
  251. log.warn('Could not find the %s element of the '
  252. 'Setuptools distribution', element)
  253. return True
  254. _remove_flat_installation = _no_sandbox(_remove_flat_installation)
  255. def _after_install(dist):
  256. log.warn('After install bootstrap.')
  257. placeholder = dist.get_command_obj('install').install_purelib
  258. _create_fake_setuptools_pkg_info(placeholder)
  259. def _create_fake_setuptools_pkg_info(placeholder):
  260. if not placeholder or not os.path.exists(placeholder):
  261. log.warn('Could not find the install location')
  262. return
  263. pyver = '%s.%s' % (sys.version_info[0], sys.version_info[1])
  264. setuptools_file = 'setuptools-%s-py%s.egg-info' % \
  265. (SETUPTOOLS_FAKED_VERSION, pyver)
  266. pkg_info = os.path.join(placeholder, setuptools_file)
  267. if os.path.exists(pkg_info):
  268. log.warn('%s already exists', pkg_info)
  269. return
  270. log.warn('Creating %s', pkg_info)
  271. f = open(pkg_info, 'w')
  272. try:
  273. f.write(SETUPTOOLS_PKG_INFO)
  274. finally:
  275. f.close()
  276. pth_file = os.path.join(placeholder, 'setuptools.pth')
  277. log.warn('Creating %s', pth_file)
  278. f = open(pth_file, 'w')
  279. try:
  280. f.write(os.path.join(os.curdir, setuptools_file))
  281. finally:
  282. f.close()
  283. _create_fake_setuptools_pkg_info = _no_sandbox(_create_fake_setuptools_pkg_info)
  284. def _patch_egg_dir(path):
  285. # let's check if it's already patched
  286. pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
  287. if os.path.exists(pkg_info):
  288. if _same_content(pkg_info, SETUPTOOLS_PKG_INFO):
  289. log.warn('%s already patched.', pkg_info)
  290. return False
  291. _rename_path(path)
  292. os.mkdir(path)
  293. os.mkdir(os.path.join(path, 'EGG-INFO'))
  294. pkg_info = os.path.join(path, 'EGG-INFO', 'PKG-INFO')
  295. f = open(pkg_info, 'w')
  296. try:
  297. f.write(SETUPTOOLS_PKG_INFO)
  298. finally:
  299. f.close()
  300. return True
  301. _patch_egg_dir = _no_sandbox(_patch_egg_dir)
  302. def _before_install():
  303. log.warn('Before install bootstrap.')
  304. _fake_setuptools()
  305. def _under_prefix(location):
  306. if 'install' not in sys.argv:
  307. return True
  308. args = sys.argv[sys.argv.index('install')+1:]
  309. for index, arg in enumerate(args):
  310. for option in ('--root', '--prefix'):
  311. if arg.startswith('%s=' % option):
  312. top_dir = arg.split('root=')[-1]
  313. return location.startswith(top_dir)
  314. elif arg == option:
  315. if len(args) > index:
  316. top_dir = args[index+1]
  317. return location.startswith(top_dir)
  318. if arg == '--user' and USER_SITE is not None:
  319. return location.startswith(USER_SITE)
  320. return True
  321. def _fake_setuptools():
  322. log.warn('Scanning installed packages')
  323. try:
  324. import pkg_resources
  325. except ImportError:
  326. # we're cool
  327. log.warn('Setuptools or Distribute does not seem to be installed.')
  328. return
  329. ws = pkg_resources.working_set
  330. try:
  331. setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools',
  332. replacement=False))
  333. except TypeError:
  334. # old distribute API
  335. setuptools_dist = ws.find(pkg_resources.Requirement.parse('setuptools'))
  336. if setuptools_dist is None:
  337. log.warn('No setuptools distribution found')
  338. return
  339. # detecting if it was already faked
  340. setuptools_location = setuptools_dist.location
  341. log.warn('Setuptools installation detected at %s', setuptools_location)
  342. # if --root or --preix was provided, and if
  343. # setuptools is not located in them, we don't patch it
  344. if not _under_prefix(setuptools_location):
  345. log.warn('Not patching, --root or --prefix is installing Distribute'
  346. ' in another location')
  347. return
  348. # let's see if its an egg
  349. if not setuptools_location.endswith('.egg'):
  350. log.warn('Non-egg installation')
  351. res = _remove_flat_installation(setuptools_location)
  352. if not res:
  353. return
  354. else:
  355. log.warn('Egg installation')
  356. pkg_info = os.path.join(setuptools_location, 'EGG-INFO', 'PKG-INFO')
  357. if (os.path.exists(pkg_info) and
  358. _same_content(pkg_info, SETUPTOOLS_PKG_INFO)):
  359. log.warn('Already patched.')
  360. return
  361. log.warn('Patching...')
  362. # let's create a fake egg replacing setuptools one
  363. res = _patch_egg_dir(setuptools_location)
  364. if not res:
  365. return
  366. log.warn('Patched done.')
  367. _relaunch()
  368. def _relaunch():
  369. log.warn('Relaunching...')
  370. # we have to relaunch the process
  371. # pip marker to avoid a relaunch bug
  372. if sys.argv[:3] == ['-c', 'install', '--single-version-externally-managed']:
  373. sys.argv[0] = 'setup.py'
  374. args = [sys.executable] + sys.argv
  375. sys.exit(subprocess.call(args))
  376. def _extractall(self, path=".", members=None):
  377. """Extract all members from the archive to the current working
  378. directory and set owner, modification time and permissions on
  379. directories afterwards. `path' specifies a different directory
  380. to extract to. `members' is optional and must be a subset of the
  381. list returned by getmembers().
  382. """
  383. import copy
  384. import operator
  385. from tarfile import ExtractError
  386. directories = []
  387. if members is None:
  388. members = self
  389. for tarinfo in members:
  390. if tarinfo.isdir():
  391. # Extract directories with a safe mode.
  392. directories.append(tarinfo)
  393. tarinfo = copy.copy(tarinfo)
  394. tarinfo.mode = 448 # decimal for oct 0700
  395. self.extract(tarinfo, path)
  396. # Reverse sort directories.
  397. if sys.version_info < (2, 4):
  398. def sorter(dir1, dir2):
  399. return cmp(dir1.name, dir2.name)
  400. directories.sort(sorter)
  401. directories.reverse()
  402. else:
  403. directories.sort(key=operator.attrgetter('name'), reverse=True)
  404. # Set correct owner, mtime and filemode on directories.
  405. for tarinfo in directories:
  406. dirpath = os.path.join(path, tarinfo.name)
  407. try:
  408. self.chown(tarinfo, dirpath)
  409. self.utime(tarinfo, dirpath)
  410. self.chmod(tarinfo, dirpath)
  411. except ExtractError:
  412. e = sys.exc_info()[1]
  413. if self.errorlevel > 1:
  414. raise
  415. else:
  416. self._dbg(1, "tarfile: %s" % e)
  417. def main(argv, version=DEFAULT_VERSION):
  418. """Install or upgrade setuptools and EasyInstall"""
  419. tarball = download_setuptools()
  420. _install(tarball)
  421. if __name__ == '__main__':
  422. main(sys.argv[1:])