setup.py 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868
  1. """
  2. Shared setup file for simple python packages. Uses a setup.cfg that
  3. is the same as the distutils2 project, unless noted otherwise.
  4. It exists for two reasons:
  5. 1) This makes it easier to reuse setup.py code between my own
  6. projects
  7. 2) Easier migration to distutils2 when that catches on.
  8. Additional functionality:
  9. * Section metadata:
  10. requires-test: Same as 'tests_require' option for setuptools.
  11. """
  12. import sys
  13. import os
  14. import re
  15. import platform
  16. from fnmatch import fnmatch
  17. import os
  18. import sys
  19. import time
  20. import tempfile
  21. import tarfile
  22. try:
  23. import urllib.request as urllib
  24. except ImportError:
  25. import urllib
  26. from distutils import log
  27. try:
  28. from hashlib import md5
  29. except ImportError:
  30. from md5 import md5
  31. if sys.version_info[0] == 2:
  32. from ConfigParser import RawConfigParser, NoOptionError, NoSectionError
  33. else:
  34. from configparser import RawConfigParser, NoOptionError, NoSectionError
  35. ROOTDIR = os.path.dirname(os.path.abspath(__file__))
  36. #
  37. #
  38. #
  39. # Parsing the setup.cfg and converting it to something that can be
  40. # used by setuptools.setup()
  41. #
  42. #
  43. #
  44. def eval_marker(value):
  45. """
  46. Evaluate an distutils2 environment marker.
  47. This code is unsafe when used with hostile setup.cfg files,
  48. but that's not a problem for our own files.
  49. """
  50. value = value.strip()
  51. class M:
  52. def __init__(self, **kwds):
  53. for k, v in kwds.items():
  54. setattr(self, k, v)
  55. variables = {
  56. 'python_version': '%d.%d'%(sys.version_info[0], sys.version_info[1]),
  57. 'python_full_version': sys.version.split()[0],
  58. 'os': M(
  59. name=os.name,
  60. ),
  61. 'sys': M(
  62. platform=sys.platform,
  63. ),
  64. 'platform': M(
  65. version=platform.version(),
  66. machine=platform.machine(),
  67. ),
  68. }
  69. return bool(eval(value, variables, variables))
  70. return True
  71. def _opt_value(cfg, into, section, key, transform = None):
  72. try:
  73. v = cfg.get(section, key)
  74. if transform != _as_lines and ';' in v:
  75. v, marker = v.rsplit(';', 1)
  76. if not eval_marker(marker):
  77. return
  78. v = v.strip()
  79. if v:
  80. if transform:
  81. into[key] = transform(v.strip())
  82. else:
  83. into[key] = v.strip()
  84. except (NoOptionError, NoSectionError):
  85. pass
  86. def _as_bool(value):
  87. if value.lower() in ('y', 'yes', 'on'):
  88. return True
  89. elif value.lower() in ('n', 'no', 'off'):
  90. return False
  91. elif value.isdigit():
  92. return bool(int(value))
  93. else:
  94. raise ValueError(value)
  95. def _as_list(value):
  96. return value.split()
  97. def _as_lines(value):
  98. result = []
  99. for v in value.splitlines():
  100. if ';' in v:
  101. v, marker = v.rsplit(';', 1)
  102. if not eval_marker(marker):
  103. continue
  104. v = v.strip()
  105. if v:
  106. result.append(v)
  107. else:
  108. result.append(v)
  109. return result
  110. def _map_requirement(value):
  111. m = re.search(r'(\S+)\s*(?:\((.*)\))?', value)
  112. name = m.group(1)
  113. version = m.group(2)
  114. if version is None:
  115. return name
  116. else:
  117. mapped = []
  118. for v in version.split(','):
  119. v = v.strip()
  120. if v[0].isdigit():
  121. # Checks for a specific version prefix
  122. m = v.rsplit('.', 1)
  123. mapped.append('>=%s,<%s.%s'%(
  124. v, m[0], int(m[1])+1))
  125. else:
  126. mapped.append(v)
  127. return '%s %s'%(name, ','.join(mapped),)
  128. def _as_requires(value):
  129. requires = []
  130. for req in value.splitlines():
  131. if ';' in req:
  132. req, marker = v.rsplit(';', 1)
  133. if not eval_marker(marker):
  134. continue
  135. req = req.strip()
  136. if not req:
  137. continue
  138. requires.append(_map_requirement(req))
  139. return requires
  140. def parse_setup_cfg():
  141. cfg = RawConfigParser()
  142. r = cfg.read([os.path.join(ROOTDIR, 'setup.cfg')])
  143. if len(r) != 1:
  144. print("Cannot read 'setup.cfg'")
  145. sys.exit(1)
  146. metadata = dict(
  147. name = cfg.get('metadata', 'name'),
  148. version = cfg.get('metadata', 'version'),
  149. description = cfg.get('metadata', 'description'),
  150. )
  151. _opt_value(cfg, metadata, 'metadata', 'license')
  152. _opt_value(cfg, metadata, 'metadata', 'maintainer')
  153. _opt_value(cfg, metadata, 'metadata', 'maintainer_email')
  154. _opt_value(cfg, metadata, 'metadata', 'author')
  155. _opt_value(cfg, metadata, 'metadata', 'author_email')
  156. _opt_value(cfg, metadata, 'metadata', 'url')
  157. _opt_value(cfg, metadata, 'metadata', 'download_url')
  158. _opt_value(cfg, metadata, 'metadata', 'classifiers', _as_lines)
  159. _opt_value(cfg, metadata, 'metadata', 'platforms', _as_list)
  160. _opt_value(cfg, metadata, 'metadata', 'packages', _as_list)
  161. _opt_value(cfg, metadata, 'metadata', 'keywords', _as_list)
  162. try:
  163. v = cfg.get('metadata', 'requires-dist')
  164. except (NoOptionError, NoSectionError):
  165. pass
  166. else:
  167. requires = _as_requires(v)
  168. if requires:
  169. metadata['install_requires'] = requires
  170. try:
  171. v = cfg.get('metadata', 'requires-test')
  172. except (NoOptionError, NoSectionError):
  173. pass
  174. else:
  175. requires = _as_requires(v)
  176. if requires:
  177. metadata['tests_require'] = requires
  178. try:
  179. v = cfg.get('metadata', 'long_description_file')
  180. except (NoOptionError, NoSectionError):
  181. pass
  182. else:
  183. parts = []
  184. for nm in v.split():
  185. fp = open(nm, 'rU')
  186. parts.append(fp.read())
  187. fp.close()
  188. metadata['long_description'] = '\n\n'.join(parts)
  189. try:
  190. v = cfg.get('metadata', 'zip-safe')
  191. except (NoOptionError, NoSectionError):
  192. pass
  193. else:
  194. metadata['zip_safe'] = _as_bool(v)
  195. try:
  196. v = cfg.get('metadata', 'console_scripts')
  197. except (NoOptionError, NoSectionError):
  198. pass
  199. else:
  200. if 'entry_points' not in metadata:
  201. metadata['entry_points'] = {}
  202. metadata['entry_points']['console_scripts'] = v.splitlines()
  203. if sys.version_info[:2] <= (2,6):
  204. try:
  205. metadata['tests_require'] += ", unittest2"
  206. except KeyError:
  207. metadata['tests_require'] = "unittest2"
  208. return metadata
  209. #
  210. #
  211. #
  212. # Bootstrapping setuptools/distribute, based on
  213. # a heavily modified version of distribute_setup.py
  214. #
  215. #
  216. #
  217. SETUPTOOLS_PACKAGE='setuptools'
  218. try:
  219. import subprocess
  220. def _python_cmd(*args):
  221. args = (sys.executable,) + args
  222. return subprocess.call(args) == 0
  223. except ImportError:
  224. def _python_cmd(*args):
  225. args = (sys.executable,) + args
  226. new_args = []
  227. for a in args:
  228. new_args.append(a.replace("'", "'\"'\"'"))
  229. os.system(' '.join(new_args)) == 0
  230. try:
  231. import json
  232. def get_pypi_src_download(package):
  233. url = 'https://pypi.python.org/pypi/%s/json'%(package,)
  234. fp = urllib.urlopen(url)
  235. try:
  236. try:
  237. data = fp.read()
  238. finally:
  239. fp.close()
  240. except urllib.error:
  241. raise RuntimeError("Cannot determine download link for %s"%(package,))
  242. pkgdata = json.loads(data.decode('utf-8'))
  243. if 'urls' not in pkgdata:
  244. raise RuntimeError("Cannot determine download link for %s"%(package,))
  245. for info in pkgdata['urls']:
  246. if info['packagetype'] == 'sdist' and info['url'].endswith('tar.gz'):
  247. return (info.get('md5_digest'), info['url'])
  248. raise RuntimeError("Cannot determine downlink link for %s"%(package,))
  249. except ImportError:
  250. # Python 2.5 compatibility, no JSON in stdlib but luckily JSON syntax is
  251. # simular enough to Python's syntax to be able to abuse the Python compiler
  252. import _ast as ast
  253. def get_pypi_src_download(package):
  254. url = 'https://pypi.python.org/pypi/%s/json'%(package,)
  255. fp = urllib.urlopen(url)
  256. try:
  257. try:
  258. data = fp.read()
  259. finally:
  260. fp.close()
  261. except urllib.error:
  262. raise RuntimeError("Cannot determine download link for %s"%(package,))
  263. a = compile(data, '-', 'eval', ast.PyCF_ONLY_AST)
  264. if not isinstance(a, ast.Expression):
  265. raise RuntimeError("Cannot determine download link for %s"%(package,))
  266. a = a.body
  267. if not isinstance(a, ast.Dict):
  268. raise RuntimeError("Cannot determine download link for %s"%(package,))
  269. for k, v in zip(a.keys, a.values):
  270. if not isinstance(k, ast.Str):
  271. raise RuntimeError("Cannot determine download link for %s"%(package,))
  272. k = k.s
  273. if k == 'urls':
  274. a = v
  275. break
  276. else:
  277. raise RuntimeError("PyPI JSON for %s doesn't contain URLs section"%(package,))
  278. if not isinstance(a, ast.List):
  279. raise RuntimeError("Cannot determine download link for %s"%(package,))
  280. for info in v.elts:
  281. if not isinstance(info, ast.Dict):
  282. raise RuntimeError("Cannot determine download link for %s"%(package,))
  283. url = None
  284. packagetype = None
  285. chksum = None
  286. for k, v in zip(info.keys, info.values):
  287. if not isinstance(k, ast.Str):
  288. raise RuntimeError("Cannot determine download link for %s"%(package,))
  289. if k.s == 'url':
  290. if not isinstance(v, ast.Str):
  291. raise RuntimeError("Cannot determine download link for %s"%(package,))
  292. url = v.s
  293. elif k.s == 'packagetype':
  294. if not isinstance(v, ast.Str):
  295. raise RuntimeError("Cannot determine download link for %s"%(package,))
  296. packagetype = v.s
  297. elif k.s == 'md5_digest':
  298. if not isinstance(v, ast.Str):
  299. raise RuntimeError("Cannot determine download link for %s"%(package,))
  300. chksum = v.s
  301. if url is not None and packagetype == 'sdist' and url.endswith('.tar.gz'):
  302. return (chksum, url)
  303. raise RuntimeError("Cannot determine download link for %s"%(package,))
  304. def _build_egg(egg, tarball, to_dir):
  305. # extracting the tarball
  306. tmpdir = tempfile.mkdtemp()
  307. log.warn('Extracting in %s', tmpdir)
  308. old_wd = os.getcwd()
  309. try:
  310. os.chdir(tmpdir)
  311. tar = tarfile.open(tarball)
  312. _extractall(tar)
  313. tar.close()
  314. # going in the directory
  315. subdir = os.path.join(tmpdir, os.listdir(tmpdir)[0])
  316. os.chdir(subdir)
  317. log.warn('Now working in %s', subdir)
  318. # building an egg
  319. log.warn('Building a %s egg in %s', egg, to_dir)
  320. _python_cmd('setup.py', '-q', 'bdist_egg', '--dist-dir', to_dir)
  321. finally:
  322. os.chdir(old_wd)
  323. # returning the result
  324. log.warn(egg)
  325. if not os.path.exists(egg):
  326. raise IOError('Could not build the egg.')
  327. def _do_download(to_dir, packagename=SETUPTOOLS_PACKAGE):
  328. tarball = download_setuptools(packagename, to_dir)
  329. version = tarball.split('-')[-1][:-7]
  330. egg = os.path.join(to_dir, '%s-%s-py%d.%d.egg'
  331. % (packagename, version, sys.version_info[0], sys.version_info[1]))
  332. if not os.path.exists(egg):
  333. _build_egg(egg, tarball, to_dir)
  334. sys.path.insert(0, egg)
  335. import setuptools
  336. setuptools.bootstrap_install_from = egg
  337. def use_setuptools():
  338. # making sure we use the absolute path
  339. return _do_download(os.path.abspath(os.curdir))
  340. def download_setuptools(packagename, to_dir):
  341. # making sure we use the absolute path
  342. to_dir = os.path.abspath(to_dir)
  343. try:
  344. from urllib.request import urlopen
  345. except ImportError:
  346. from urllib2 import urlopen
  347. chksum, url = get_pypi_src_download(packagename)
  348. tgz_name = os.path.basename(url)
  349. saveto = os.path.join(to_dir, tgz_name)
  350. src = dst = None
  351. if not os.path.exists(saveto): # Avoid repeated downloads
  352. try:
  353. log.warn("Downloading %s", url)
  354. src = urlopen(url)
  355. # Read/write all in one block, so we don't create a corrupt file
  356. # if the download is interrupted.
  357. data = src.read()
  358. if chksum is not None:
  359. data_sum = md5(data).hexdigest()
  360. if data_sum != chksum:
  361. raise RuntimeError("Downloading %s failed: corrupt checksum"%(url,))
  362. dst = open(saveto, "wb")
  363. dst.write(data)
  364. finally:
  365. if src:
  366. src.close()
  367. if dst:
  368. dst.close()
  369. return os.path.realpath(saveto)
  370. def _extractall(self, path=".", members=None):
  371. """Extract all members from the archive to the current working
  372. directory and set owner, modification time and permissions on
  373. directories afterwards. `path' specifies a different directory
  374. to extract to. `members' is optional and must be a subset of the
  375. list returned by getmembers().
  376. """
  377. import copy
  378. import operator
  379. from tarfile import ExtractError
  380. directories = []
  381. if members is None:
  382. members = self
  383. for tarinfo in members:
  384. if tarinfo.isdir():
  385. # Extract directories with a safe mode.
  386. directories.append(tarinfo)
  387. tarinfo = copy.copy(tarinfo)
  388. tarinfo.mode = 448 # decimal for oct 0700
  389. self.extract(tarinfo, path)
  390. # Reverse sort directories.
  391. if sys.version_info < (2, 4):
  392. def sorter(dir1, dir2):
  393. return cmp(dir1.name, dir2.name)
  394. directories.sort(sorter)
  395. directories.reverse()
  396. else:
  397. directories.sort(key=operator.attrgetter('name'), reverse=True)
  398. # Set correct owner, mtime and filemode on directories.
  399. for tarinfo in directories:
  400. dirpath = os.path.join(path, tarinfo.name)
  401. try:
  402. self.chown(tarinfo, dirpath)
  403. self.utime(tarinfo, dirpath)
  404. self.chmod(tarinfo, dirpath)
  405. except ExtractError:
  406. e = sys.exc_info()[1]
  407. if self.errorlevel > 1:
  408. raise
  409. else:
  410. self._dbg(1, "tarfile: %s" % e)
  411. #
  412. #
  413. #
  414. # Definitions of custom commands
  415. #
  416. #
  417. #
  418. try:
  419. import setuptools
  420. except ImportError:
  421. use_setuptools()
  422. from setuptools import setup
  423. try:
  424. from distutils.core import PyPIRCCommand
  425. except ImportError:
  426. PyPIRCCommand = None # Ancient python version
  427. from distutils.core import Command
  428. from distutils.errors import DistutilsError
  429. from distutils import log
  430. if PyPIRCCommand is None:
  431. class upload_docs (Command):
  432. description = "upload sphinx documentation"
  433. user_options = []
  434. def initialize_options(self):
  435. pass
  436. def finalize_options(self):
  437. pass
  438. def run(self):
  439. raise DistutilsError("not supported on this version of python")
  440. else:
  441. class upload_docs (PyPIRCCommand):
  442. description = "upload sphinx documentation"
  443. user_options = PyPIRCCommand.user_options
  444. def initialize_options(self):
  445. PyPIRCCommand.initialize_options(self)
  446. self.username = ''
  447. self.password = ''
  448. def finalize_options(self):
  449. PyPIRCCommand.finalize_options(self)
  450. config = self._read_pypirc()
  451. if config != {}:
  452. self.username = config['username']
  453. self.password = config['password']
  454. def run(self):
  455. import subprocess
  456. import shutil
  457. import zipfile
  458. import os
  459. import urllib
  460. import StringIO
  461. from base64 import standard_b64encode
  462. import httplib
  463. import urlparse
  464. # Extract the package name from distutils metadata
  465. meta = self.distribution.metadata
  466. name = meta.get_name()
  467. # Run sphinx
  468. if os.path.exists('doc/_build'):
  469. shutil.rmtree('doc/_build')
  470. os.mkdir('doc/_build')
  471. p = subprocess.Popen(['make', 'html'],
  472. cwd='doc')
  473. exit = p.wait()
  474. if exit != 0:
  475. raise DistutilsError("sphinx-build failed")
  476. # Collect sphinx output
  477. if not os.path.exists('dist'):
  478. os.mkdir('dist')
  479. zf = zipfile.ZipFile('dist/%s-docs.zip'%(name,), 'w',
  480. compression=zipfile.ZIP_DEFLATED)
  481. for toplevel, dirs, files in os.walk('doc/_build/html'):
  482. for fn in files:
  483. fullname = os.path.join(toplevel, fn)
  484. relname = os.path.relpath(fullname, 'doc/_build/html')
  485. print ("%s -> %s"%(fullname, relname))
  486. zf.write(fullname, relname)
  487. zf.close()
  488. # Upload the results, this code is based on the distutils
  489. # 'upload' command.
  490. content = open('dist/%s-docs.zip'%(name,), 'rb').read()
  491. data = {
  492. ':action': 'doc_upload',
  493. 'name': name,
  494. 'content': ('%s-docs.zip'%(name,), content),
  495. }
  496. auth = "Basic " + standard_b64encode(self.username + ":" +
  497. self.password)
  498. boundary = '--------------GHSKFJDLGDS7543FJKLFHRE75642756743254'
  499. sep_boundary = '\n--' + boundary
  500. end_boundary = sep_boundary + '--'
  501. body = StringIO.StringIO()
  502. for key, value in data.items():
  503. if not isinstance(value, list):
  504. value = [value]
  505. for value in value:
  506. if isinstance(value, tuple):
  507. fn = ';filename="%s"'%(value[0])
  508. value = value[1]
  509. else:
  510. fn = ''
  511. body.write(sep_boundary)
  512. body.write('\nContent-Disposition: form-data; name="%s"'%key)
  513. body.write(fn)
  514. body.write("\n\n")
  515. body.write(value)
  516. body.write(end_boundary)
  517. body.write('\n')
  518. body = body.getvalue()
  519. self.announce("Uploading documentation to %s"%(self.repository,), log.INFO)
  520. schema, netloc, url, params, query, fragments = \
  521. urlparse.urlparse(self.repository)
  522. if schema == 'http':
  523. http = httplib.HTTPConnection(netloc)
  524. elif schema == 'https':
  525. http = httplib.HTTPSConnection(netloc)
  526. else:
  527. raise AssertionError("unsupported schema "+schema)
  528. data = ''
  529. loglevel = log.INFO
  530. try:
  531. http.connect()
  532. http.putrequest("POST", url)
  533. http.putheader('Content-type',
  534. 'multipart/form-data; boundary=%s'%boundary)
  535. http.putheader('Content-length', str(len(body)))
  536. http.putheader('Authorization', auth)
  537. http.endheaders()
  538. http.send(body)
  539. except socket.error:
  540. e = socket.exc_info()[1]
  541. self.announce(str(e), log.ERROR)
  542. return
  543. r = http.getresponse()
  544. if r.status in (200, 301):
  545. self.announce('Upload succeeded (%s): %s' % (r.status, r.reason),
  546. log.INFO)
  547. else:
  548. self.announce('Upload failed (%s): %s' % (r.status, r.reason),
  549. log.ERROR)
  550. print ('-'*75)
  551. print (r.read())
  552. print ('-'*75)
  553. def recursiveGlob(root, pathPattern):
  554. """
  555. Recursively look for files matching 'pathPattern'. Return a list
  556. of matching files/directories.
  557. """
  558. result = []
  559. for rootpath, dirnames, filenames in os.walk(root):
  560. for fn in filenames:
  561. if fnmatch(fn, pathPattern):
  562. result.append(os.path.join(rootpath, fn))
  563. return result
  564. def importExternalTestCases(unittest,
  565. pathPattern="test_*.py", root=".", package=None):
  566. """
  567. Import all unittests in the PyObjC tree starting at 'root'
  568. """
  569. testFiles = recursiveGlob(root, pathPattern)
  570. testModules = map(lambda x:x[len(root)+1:-3].replace('/', '.'), testFiles)
  571. if package is not None:
  572. testModules = [(package + '.' + m) for m in testModules]
  573. suites = []
  574. for modName in testModules:
  575. try:
  576. module = __import__(modName)
  577. except ImportError:
  578. print("SKIP %s: %s"%(modName, sys.exc_info()[1]))
  579. continue
  580. if '.' in modName:
  581. for elem in modName.split('.')[1:]:
  582. module = getattr(module, elem)
  583. s = unittest.defaultTestLoader.loadTestsFromModule(module)
  584. suites.append(s)
  585. return unittest.TestSuite(suites)
  586. class test (Command):
  587. description = "run test suite"
  588. user_options = [
  589. ('verbosity=', None, "print what tests are run"),
  590. ]
  591. def initialize_options(self):
  592. self.verbosity='1'
  593. def finalize_options(self):
  594. if isinstance(self.verbosity, str):
  595. self.verbosity = int(self.verbosity)
  596. def cleanup_environment(self):
  597. ei_cmd = self.get_finalized_command('egg_info')
  598. egg_name = ei_cmd.egg_name.replace('-', '_')
  599. to_remove = []
  600. for dirname in sys.path:
  601. bn = os.path.basename(dirname)
  602. if bn.startswith(egg_name + "-"):
  603. to_remove.append(dirname)
  604. for dirname in to_remove:
  605. log.info("removing installed %r from sys.path before testing"%(
  606. dirname,))
  607. sys.path.remove(dirname)
  608. def add_project_to_sys_path(self):
  609. from pkg_resources import normalize_path, add_activation_listener
  610. from pkg_resources import working_set, require
  611. self.reinitialize_command('egg_info')
  612. self.run_command('egg_info')
  613. self.reinitialize_command('build_ext', inplace=1)
  614. self.run_command('build_ext')
  615. # Check if this distribution is already on sys.path
  616. # and remove that version, this ensures that the right
  617. # copy of the package gets tested.
  618. self.__old_path = sys.path[:]
  619. self.__old_modules = sys.modules.copy()
  620. ei_cmd = self.get_finalized_command('egg_info')
  621. sys.path.insert(0, normalize_path(ei_cmd.egg_base))
  622. sys.path.insert(1, os.path.dirname(__file__))
  623. # Strip the namespace packages defined in this distribution
  624. # from sys.modules, needed to reset the search path for
  625. # those modules.
  626. nspkgs = getattr(self.distribution, 'namespace_packages')
  627. if nspkgs is not None:
  628. for nm in nspkgs:
  629. del sys.modules[nm]
  630. # Reset pkg_resources state:
  631. add_activation_listener(lambda dist: dist.activate())
  632. working_set.__init__()
  633. require('%s==%s'%(ei_cmd.egg_name, ei_cmd.egg_version))
  634. def remove_from_sys_path(self):
  635. from pkg_resources import working_set
  636. sys.path[:] = self.__old_path
  637. sys.modules.clear()
  638. sys.modules.update(self.__old_modules)
  639. working_set.__init__()
  640. def run(self):
  641. import unittest
  642. # Ensure that build directory is on sys.path (py3k)
  643. self.cleanup_environment()
  644. self.add_project_to_sys_path()
  645. try:
  646. meta = self.distribution.metadata
  647. name = meta.get_name()
  648. test_pkg = name + "_tests"
  649. suite = importExternalTestCases(unittest,
  650. "test_*.py", test_pkg, test_pkg)
  651. runner = unittest.TextTestRunner(verbosity=self.verbosity)
  652. result = runner.run(suite)
  653. # Print out summary. This is a structured format that
  654. # should make it easy to use this information in scripts.
  655. summary = dict(
  656. count=result.testsRun,
  657. fails=len(result.failures),
  658. errors=len(result.errors),
  659. xfails=len(getattr(result, 'expectedFailures', [])),
  660. xpass=len(getattr(result, 'expectedSuccesses', [])),
  661. skip=len(getattr(result, 'skipped', [])),
  662. )
  663. print("SUMMARY: %s"%(summary,))
  664. finally:
  665. self.remove_from_sys_path()
  666. #
  667. #
  668. #
  669. # And finally run the setuptools main entry point.
  670. #
  671. #
  672. #
  673. metadata = parse_setup_cfg()
  674. setup(
  675. cmdclass=dict(
  676. upload_docs=upload_docs,
  677. test=test,
  678. ),
  679. **metadata
  680. )