distribute_setup.py 17 KB

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