--- /dev/null
+[run]
+branch = True
+source = pbr
+omit = pbr/tests/*
+
+[report]
+ignore_errors = True
+precision = 2
--- /dev/null
+# Format is:
+# <preferred e-mail> <other e-mail 1>
+# <preferred e-mail> <other e-mail 2>
+Davanum Srinivas <dims@linux.vnet.ibm.com> <davanum@gmail.com>
+Erik M. Bray <embray@stsci.edu> Erik Bray <embray@stsci.edu>
+Zhongyue Luo <zhongyue.nah@intel.com> <lzyeval@gmail.com>
--- /dev/null
+[DEFAULT]
+test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-60} ${PYTHON:-python} -m subunit.run discover -t ./ . $LISTOPT $IDOPTION
+test_id_option=--load-list $IDFILE
+test_list_option=--list
--- /dev/null
+Akihiro Motoki <motoki@da.jp.nec.com>
+Alex Gaynor <alex.gaynor@gmail.com>
+Alexander Makarov <amakarov@mirantis.com>
+Andreas Jaeger <aj@suse.de>
+Andrew Bogott <abogott@wikimedia.org>
+Angus Salkeld <asalkeld@redhat.com>
+Anthony Young <sleepsonthefloor@gmail.com>
+Attila Fazekas <afazekas@redhat.com>
+Ben Nemec <bnemec@redhat.com>
+Bhuvan Arumugam <bhuvan@apache.org>
+Brant Knudson <bknudson@us.ibm.com>
+Brian Waldon <bcwaldon@gmail.com>
+Cao Xuan Hoang <hoangcx@vn.fujitsu.com>
+Chang Bo Guo <guochbo@cn.ibm.com>
+ChangBo Guo(gcb) <eric.guo@easystack.cn>
+Christian Berendt <berendt@b1-systems.de>
+Chuck Short <chuck.short@canonical.com>
+Clark Boylan <clark.boylan@gmail.com>
+Claudiu Popa <cpopa@cloudbasesolutions.com>
+Dan Prince <dprince@redhat.com>
+Darragh Bailey <dbailey@hp.com>
+Davanum Srinivas <dims@linux.vnet.ibm.com>
+Dave Walker (Daviey) <email@daviey.com>
+David Ripton <dripton@redhat.com>
+David Stanek <dstanek@dstanek.com>
+Devananda van der Veen <devananda.vdv@gmail.com>
+Dirk Mueller <dirk@dmllr.de>
+Doug Hellmann <doug.hellmann@dreamhost.com>
+Doug Hellmann <doug.hellmann@gmail.com>
+Doug Hellmann <doug@doughellmann.com>
+Dougal Matthews <dougal@redhat.com>
+Elena Ezhova <eezhova@mirantis.com>
+Eoghan Glynn <eglynn@redhat.com>
+Eric Windisch <eric@cloudscaling.com>
+Erik M. Bray <embray@stsci.edu>
+Eugene Kirpichov <ekirpichov@gmail.com>
+Florian Wilhelm <Florian.Wilhelm@blue-yonder.com>
+Gary Kotton <gkotton@redhat.com>
+Giampaolo Lauria <lauria@us.ibm.com>
+Ian Cordasco <graffatcolmingov@gmail.com>
+Ian Wienand <iwienand@redhat.com>
+Ian Y. Choi <ianyrchoi@gmail.com>
+Ionuț Arțăriși <iartarisi@suse.cz>
+James E. Blair <jeblair@hp.com>
+James Polley <jp@jamezpolley.com>
+Jason Kölker <jason@koelker.net>
+Jay Pipes <jaypipes@gmail.com>
+Jeremy Stanley <fungi@yuggoth.org>
+Joe Gordon <joe.gordon0@gmail.com>
+Joe Gordon <jogo@cloudscaling.com>
+Joe Heck <heckj@mac.com>
+Johannes Erdfelt <johannes.erdfelt@rackspace.com>
+Joshua Harlow <harlowja@gmail.com>
+Joshua Harlow <harlowja@yahoo-inc.com>
+Joshua Harlow <jxharlow@godaddy.com>
+Julien Danjou <julien@danjou.info>
+Kevin McCarthy <me@kevinmccarthy.org>
+Khai Do <zaro0508@gmail.com>
+Laurence Miao <laurence.miao@gmail.com>
+Lucian Petrut <lpetrut@cloudbasesolutions.com>
+Luo Gangyi <luogangyi@chinamobile.com>
+Marc Abramowitz <marc@marc-abramowitz.com>
+Mark McLoughlin <markmc@redhat.com>
+Mark Sienkiewicz <sienkiew@stsci.edu>
+Maru Newby <marun@redhat.com>
+Masaki Matsushita <glass.saga@gmail.com>
+Matt Riedemann <mriedem@us.ibm.com>
+Matthew Treinish <treinish@linux.vnet.ibm.com>
+Michael Basnight <mbasnight@gmail.com>
+Michael Still <mikal@stillhq.com>
+Mike Heald <mike.heald@hp.com>
+Monty Taylor <mordred@inaugust.com>
+Nikhil Manchanda <SlickNik@gmail.com>
+Octavian Ciuhandu <ociuhandu@cloudbasesolutions.com>
+Ondřej Nový <ondrej.novy@firma.seznam.cz>
+Paul Belanger <pabelanger@redhat.com>
+Rajaram Mallya <rajarammallya@gmail.com>
+Ralf Haferkamp <rhafer@suse.de>
+Randall Nortman <openstack@nortman.net>
+Rick Harris <rconradharris@gmail.com>
+Robert Collins <rbtcollins@hp.com>
+Robert Myers <robert.myers@rackspace.com>
+Roger Luethi <rl@patchworkscience.org>
+Ronald Bradford <ronald.bradford@gmail.com>
+Ruby Loo <rloo@yahoo-inc.com>
+Russell Bryant <rbryant@redhat.com>
+Ryan Bourgeois <bluedragonx@gmail.com>
+Ryan Petrello <lists@ryanpetrello.com>
+Sachi King <nakato@nakato.io>
+Sascha Peilicke <speilicke@suse.com>
+Sean Dague <sdague@linux.vnet.ibm.com>
+Sean Dague <sean@dague.net>
+Sergey Lukjanov <slukjanov@mirantis.com>
+Stephen Finucane <sfinucan@redhat.com>
+Stephen Finucane <stephen.finucane@intel.com>
+Steve Kowalik <steven@wedontsleep.org>
+Steve Martinelli <stevemar@ca.ibm.com>
+Steven Hardy <shardy@redhat.com>
+Thomas Bechtold <tbechtold@suse.com>
+Thomas Goirand <thomas@goirand.fr>
+Thomas Grainger <tagrain@gmail.com>
+Thomas Herve <therve@redhat.com>
+Thomas Leaman <thomas.leaman@hp.com>
+Thomas Morin <thomas.morin@orange.com>
+Tim Simpson <tim.simpson@rackspace.com>
+Timothy Chavez <timothy.chavez@hp.com>
+Toilal <toilal.dev@gmail.com>
+Vasudev Kamath <kamathvasudev@gmail.com>
+Vincent Untz <vuntz@suse.com>
+Vishvananda Ishaya <vishvananda@gmail.com>
+YAMAMOTO Takashi <yamamoto@valinux.co.jp>
+Yaguang Tang <heut2008@gmail.com>
+Yuriy Taraday <yorik.sar@gmail.com>
+Zhongyue Luo <zhongyue.nah@intel.com>
+alexpilotti <ap@pilotti.it>
+cbjchen@cn.ibm.com <cbjchen@cn.ibm.com>
+dineshbhor <dinesh.bhor@nttdata.com>
+jiansong <jian.song@easystack.cn>
+lifeless <robertc@robertcollins.net>
+melanie witt <melwitt@yahoo-inc.com>
+nizam <abdul.nizamuddin@nectechnologies.in>
+weiweigu <gu.weiwei@zte.com.cn>
+zhangyanxian <zhang.yanxian@zte.com.cn>
--- /dev/null
+If you would like to contribute to the development of OpenStack,
+you must follow the steps in this page:
+
+ http://docs.openstack.org/infra/manual/developers.html
+
+Once those steps have been completed, changes to OpenStack
+should be submitted for review via the Gerrit tool, following
+the workflow documented at:
+
+ http://docs.openstack.org/infra/manual/developers.html#development-workflow
+
+Pull requests submitted through GitHub will be ignored.
+
+Bugs should be filed on Launchpad, not GitHub:
+
+ https://bugs.launchpad.net/pbr
--- /dev/null
+CHANGES
+=======
+
+2.0.0
+-----
+
+* tox: Don't set skipsdist=True
+* Stop using 'warnerrors'
+* doc: Clarify sections in 'setup.cfg'
+* Updated from global requirements
+* Remove discover from test-requirements
+* Add Constraints support
+* Don't raise exception on missing man pages
+* Updated from global requirements
+* Clean imports in code
+* Updated from global requirements
+* Docstrings should not start with a space
+* Changed the home-page link
+* Update .coveragerc after the removal of openstack directory
+* coverage package name option, doc improvement
+* Updated from global requirements
+* Deprecated warning for SafeConfigParser
+* Add more words to a confusing error message
+* Don't ignore data-files
+* Change assertTrue(isinstance()) by optimal assert
+* Fix handling of old git log output
+* Fix typo in the index.rst
+* Expose deb version to match exposing rpm version
+* Replace OpenStack LLC with OpenStack Foundation
+* Updated from global requirements
+* Fix pypy soabi tests
+* Add Python 3.5 classifier and venv
+* Fix argument order for assertEqual to (expected, observed)
+* Move to oslosphinx
+* Updated from global requirements
+* Restore warnerrors behavior and support Sphinx 1.4
+* Updated from global requirements
+* Updated from global requirements
+* Updated from global requirements
+* Fix pypy wsgi tests
+* Remember the insertion order for pbr.json
+
+1.10.0
+------
+
+* File is wrongly marked as executable
+* Fix wsgiref script use with oslo.config
+* Update Preversioning explanation to avoid double that
+
+1.9.1
+-----
+
+* Handle IndexError during version string parsing
+* Correct server test
+
+1.9.0
+-----
+
+* Updated from global requirements
+* Fix soabi tests with pypy
+* package: fix wrong catch in email parsing
+* Sort 'pbr freeze' output
+* Do not convert git tags when searching history
+* Include wsgi_scripts in generated wheels
+* Correct the indentation in the classifiers example
+* Do not silently drop markers that fail to evaluate
+* Clarifications around tags and version numbers
+* Correct typo - s/enabeld/enabled/
+* Use apt-cache generated packages to provide build deps
+* fix some variable names
+* Don't attempt to test with 0.6c11 with Py3
+* Support entry point patching on setuptools < 12
+* Updated from global requirements
+* Split changelog on nulls instead of (
+* Add libjpeg and liberasurecode for tests
+* Handle markers to support sdist on pip < 6
+* Deprecated tox -downloadcache option removed
+* passenv integration environment variables re-enabling integration tests
+* Enable pep8 H405 tests
+* Add patch to properly get all commands from dist
+* doc: Remove 'MANIFEST.in'
+* doc: Trivial cleanup of 'index.rst'
+* doc: Add deprecation note for 'requirements-pyN'
+* doc: Restructure 'Requirements' section
+* doc: Restructure 'Usage' section
+* doc: Add details of manifest generation
+* Support git://, git+ssh://, git+https:// without -e flag
+* More support Sphinx >=1.3b1 and <1.3.1
+* Fix docs for markers
+* Do not error when running pep8 with py3
+* Ensure changelog log output is written if it already exists
+* Cleanup jeepyb and pypi-mirror special casing
+
+1.8.1
+-----
+
+* Handle the case where cmd.distribution has no pbr attribute
+* test_integration cleanups
+* Remove redundant testscenarios glue
+* Add standard code coverage configuration file
+* Add shields.io version/downloads links/badges into README.rst
+
+1.8.0
+-----
+
+* Strip comments present in setup.cfg
+* Protect WSGI application with a critical section
+
+1.7.0
+-----
+
+* Have pbr egg_info.writer check PBR is enabled
+
+1.6.0
+-----
+
+* Strip comments in requirements files
+* Support Sphinx >=1.3 new protoype and warnings
+
+1.5.0
+-----
+
+* Handle git being entirely absent
+* We require the use of setuptools
+* Fix retrieval of commit data and most recent tag
+
+1.4.0
+-----
+
+* Fix docs for environment markers in extras
+* Export ChangeLog and AUTHORS in install
+* Updated from global requirements
+* Updated from global requirements
+* Show how long the git querying takes
+* Add wsgi_scripts support to PBR
+* Updated from global requirements
+
+1.3.0
+-----
+
+* Wrap env markers in parens
+* Updated from global requirements
+* Add more documentation around building docs
+* Expose a 'rpm_version' extra command
+* Updated from global requirements
+* Use string for 'OS_TEST_TIMEOUT' default
+* Updated from global requirements
+* This documents the use of comments that work between 2.6, 2.7 and 3.4
+
+1.2.0
+-----
+
+* Strip markers from test requirements
+* Add build_sphinx test coverage
+
+1.1.1
+-----
+
+* Revert "Remove sphinx_config.init_values() manual call"
+
+1.1.0
+-----
+
+* Fix test case to be runnable with gnupg 2.1
+* More explicit data_files install location docs
+* Move CapturedSubprocess fixture to base
+* Remove sphinx_config.init_values() manual call
+* Updated from global requirements
+* builddoc: allow to use fnmatch-style exclusion for autodoc
+* doc: add some basic doc about pbr doc options
+* Add home-page into sample setup.cfg
+* Make setup.py --help-commands work without testrepository
+
+1.0.1
+-----
+
+* Remove self.pre_run calls in packaging.py
+* Add kerberos deps to build the kerberos wheel
+* Update hacking to 0.10.x series
+
+1.0.0
+-----
+
+* Teach pbr to read extras and env markers
+* Bump integration test timeouts
+* Finish removing invocations of pip
+* Advertise support for Python3.4
+* Issue #1451976: handle commits with non-utf8 text
+* Parallelise integration tests
+
+0.11.0
+------
+
+* Issue #1450210: Preversioning rc tag handling
+* Stop testing setup.py easy_install behaviour
+* Test pip install -e of projects
+* Build all the wheels in one pass rather than many
+* Improve integration.sh
+* Stop re-invoking pip
+* Honour new test variable PIPVERSION
+* Be safe, don't let friends use RawConfigParser
+* Revert "Support platform-specific requirements files"
+* Reinstate pure-git-hashes-are-illegal test
+* Ignore non-release related tags
+* Test that MANIFEST.in excludes work
+* Fixup semver
+* Better diagnostics on test failure
+* Don't avoid egg-info quite so often
+* Add truncated lincense text
+* Allow overwriting sphinx builder from command line
+* "packages" might list multiple directories
+* Support script text override for newer develop
+* Fixes Windows generated scripts headers
+* Update README format to match release notes tool
+* Be more aggressive about building wheels
+* Use a wheelhouse in the integration test
+* Build a wheel when integration testing PBR changes
+* Use unsafe OpenPGP keys for testing
+
+0.10.7
+------
+
+* _get_revno only returns a commit count
+* Move write_pbr_json to avoid issues with nose
+* Properly check for git before getting git dir
+* Port in git sha changes from 0.10 line
+
+0.10.6
+------
+
+* Integration test PBR commits
+
+0.10.5
+------
+
+* Move write_pbr_json to avoid issues with nose
+
+0.10.4
+------
+
+* Properly check for git before getting git dir
+* Use post version signifiers
+* Only import sphinx during hook processing
+
+0.10.3
+------
+
+* Stop including git sha in version strings
+* Write and read more complex git sha info
+
+0.10.1
+------
+
+* Prefix git suffixes with + instead of
+* Workflow documentation is now in infra-manual
+* Packagers vs package maintainers
+* Support platform-specific requirements files
+* Remove extraneous vim editor configuration comments
+* Clean up links and docs
+* Add user.name to git config in tests
+* Honour validly tagged versions
+* autodoc: allow to exclude modules from auto-generation
+* tests: factorize has_opt handling
+* Support reading versions from wheels
+* Only consider tags that look like versions
+* Refactor oneline changelog walking
+* Stop using intersphinx
+* Retry the integration setup on connection error
+* Fixes a spelling error
+* Adds option for excluding files from autodoc trees
+* Allow for inclusion of authors and changelog when building docs
+* Work toward Python 3.4 support and testing
+* Accept capitalized Sem-Ver headers
+* use get_boolean_option for warnerrors
+* Handle more legacy version numbers
+* Look for and process sem-ver pseudo headers in git
+* Raise an error if preversion versions are too low
+* Teach pbr about post versioned dev versions
+* Handle more local dev version cases
+* Introduce a SemanticVersion object
+* cleanup tox.ini
+* add docs env to tox
+* Bump hacking to 0.9.x series
+
+0.10.2
+------
+
+* Remove all 2.7 filtering
+* Stop filtering out argparse
+* Remove mirror testing from the integration script
+
+0.9.0
+-----
+
+* Allow examining parsing exceptions
+* Update integration script for Apache 2.4
+* Restore Monkeypatched Distribution Instance
+* Register testr as a distutil entry point
+* Check for git before querying it for a version
+* Allow _run_cmd to run commands in any directory
+* Make setUp fail if sdist fails
+* Permit pre-release versions with git metadata
+* Un-nest some sections of code
+
+0.8.2
+-----
+
+* Remove --use-mailmap as it's not needed
+* Fix typos in docs
+
+0.8.1
+-----
+
+* pbr/testr_command.py: Add logging
+* Documentation updates
+* Fixed a typo in the documentation
+* Make source configurable when using --coverage
+* README.rst: tweaks
+* Format autoindex.rst file properly
+* make pbr use hacking directly
+
+0.8.0
+-----
+
+* Use unicode_literals import instead of u'unicode' notation
+* Add support for nested requirements files
+* Remove pip version specifier
+* Make tools/integration.sh take a branch
+* Fixes blocking issue on Windows
+* Remove duplicate git setup in tests
+
+0.7.0
+-----
+
+* Factor run_cmd out of the base class
+* Return the real class in VersionInfo __repr__
+* Fix up some docstrings
+* Init sphinx config values before accessing them
+* Remove copyright from empty files
+* Declare support for Python versions in setup.cfg
+* Updated from global requirements
+* Remove unused _parse_mailmap()
+* Add support for python 3-<3.3
+* Remove tox locale overrides
+* Do not force log verbosity level to info
+
+0.6
+---
+
+* package: read a specific Python version requirement file
+* Fix typos in documents
+* Pin sphinx to <1.2
+* Build mirror using proper requirements
+* Use wheels for installation
+* show diff between pip installed packages and requirements
+* Enable wheel processing in the tests
+* Clean up integration script
+* Let git do mailmap mapping for us
+* Make ChangeLog more condensed
+* Make sphinx builders configurable in LocalBuildDoc
+* Serve local mirror using apache
+* Bump the development status classifier
+* Support building wheels (PEP-427)
+* Ignore jenkins@openstack.org in authors building
+* Communicate to user when we skip a requirement
+* Base python 2.7 skip list on parsed names
+* Link to the documentation in the readme
+* Avoid using 'which' executable for finding git
+* Add wheel mirror structure awareness
+* Remove pip as an argument from mkvenv
+* Use pypi-mirror instead of jeepyb
+
+0.5.23
+------
+
+* Fixed pbr install to not error out and fail if git is not installed
+
+0.5.22
+------
+
+* Move base test case logic out of __init__.py
+* Comply with pep440
+* Prevent ordereddict installation on 2.7
+* Do not pass unicode where byte strings are wanted
+* Add the semver documentation
+* Get rid of PyPI URL override in integration test
+* Add a test for command registration
+* Switch away from tearDown for BaseTests
+* Fix test_changelog when git isn't globally setup
+* Rework run_shell_command
+* Update tox config to use latest upgrades
+* Add a hint for users who don't have git installed
+* Add pypy to tox.ini
+* Fix python-ldap mirroring
+* Stop checking periods in commit messages
+* Fixes issue with command escaping on Windows
+* Added documentation for packagers
+* Update requirements in integration test
+* Sync requirements with global requirements
+* Fix pep8 error
+* Add option to run testr serially
+
+0.5.21
+------
+
+* Replace setuptools_git with a smarter approach
+* Clean up some style warnings
+* Consume the OpenStack mirror by default
+* Correct typo
+* Improve AUTHORS file generation
+* Ensure _run_shell_command always returns same type
+* Hierarchical sphinx API documentation generation
+* Add support for classmethod console scripts
+* Add skip_pip_install to setup.cfg
+* Remove missing_reuqires optimization
+* Use the same logic everywhere for requirements files
+
+0.5.20
+------
+
+* Fix .mailmap file search location
+* Swap the order of configparser imports
+* Install jeepyb and requirements from REPODIR
+* Stop trying to install old pip and setuptools
+* Add support for globbing in data files
+* Revert include_package_data change
+* Also patch easy_install script creation
+* Loop over test output for better readability
+* Add more documentation
+* We force installs via pip, we should declare it
+* Fix python 3.3 tests
+* Replace entry_points console_scripts
+* Fix integer_types type under python 3
+* Remove the need to specify the pbr hook
+* Move d2to1 more into the source tree
+* Set defaults directly in option processing
+* Do not assume the tests run as jenkins
+* Add libffi-dev
+* Ignore project creator while generating AUTHORS
+
+0.5.19
+------
+
+* Add Python 3.3 checking
+* Fix some issues in setup.py test
+* Put AUTHORS.in at the top of AUTHORS
+* Support version override with PBR prefix
+* Don't try to install requirements repo
+* Allow pip installation to be skipped
+* Don't run pip needlessly
+* Clean up hacking and path issues with d2to1
+* Support version override with PBR prefix
+* Fix some issues in setup.py test
+* Add support for namespace packages
+
+0.5.18
+------
+
+* testr: fix Python 3 compatibility
+* Allow to override requirement files via environ
+* Add importlib to the filter list
+* Fix integration script
+* Move testrepository to test-requirements.txt
+* Make python setup.py test do the right thing
+* Add an integration test script
+
+0.5.17
+------
+
+* Fix pip invocation for old versions of pip
+
+0.5.16
+------
+
+* Remove explicit depend on distribute
+
+0.5.15
+------
+
+* Use pip instead of easy_install for installation
+* Skip git-checkout related tests when .git is missing
+* Refactor hooks file
+
+0.5.14
+------
+
+* Remove jinja pin
+
+0.5.12
+------
+
+* Explicitly install install_requires
+* More python3 compatibility
+* python3: Use six everywhere
+* Add vim and emacs files to .gitignore
+* Fix sphinx autodoc module headers
+* Handle versioned egg fragments in dependency URLs
+* Fix tox.ini to not reference deleted testenv
+
+0.5.11
+------
+
+* Fix author and changelog skipping
+* Fix doc autoindexing
+* Changed _get_git_directory() to use git itself
+* python3: Python3.x compatibility
+* Cosmetic doc fixes
+* Make parse errors meaningful
+* Add missing files to the tarball
+
+0.5.10
+------
+
+* Default to single-version-externally-managed
+* Add config for skipping authors and changelog
+* Update to d2to1 0.2.10
+* Added some additional zest.releaser hooks to deal with some perpetual annoyances of doing releases
+* When catching exceptions from a setup-hook don't throw a traceback up for sys.exit()
+
+0.5.8
+-----
+
+* Don't try to smart find packages that aren't there
+* Add support for manpages
+* Support forcing single-version-externally-managed
+* Add more iterations to BuildDoc coverage
+* Align Sphinx autodoc options with oslo
+
+0.5.6
+-----
+
+* Using __file__ was getting the wrong location
+* Add support for configuring pbr sphinx options
+* Add support for warnings=errors
+* Fix authors generation from co-authored-by
+* Ported in Co-authored-by support from oslo
+* Fix up the test suite to actually all pass
+* Added version code
+* Update tox to run hacking
+* Make hacking compliant
+* Ensure that reuqirements files are shipped
+* Add MANIFEST.in
+
+0.5.0
+-----
+
+* Update documentation for release
+* Rename back to PBR
+* Remove extra complexity of six.u wrapper
+* Add contributing file
+* Fix .gitreview file
+* Add docs dir
+* Cleaned up stdout capture invocation
+* Put stdout capture in the base test class
+* Amended README testing instructions
+* Update README with test running instructions
+* Move setup_requires to setup_requires_dist
+* Move sphinx to test-reuqirements
+* Deal with Sphinx import ordering
+* Add a better todo comment
+* Update docs
+* Just use d2to1 via hooks mechanism
+* Add a few more backwards compatibility options
+* Fix exception handling error
+* Lower distribute requirement - it's too extreme
+* Remove sex requirement, since it's in extern
+* Remove version that had been used for testing
+* pep8/pyflakes fixes
+* Fix things up to work with nova
+* Split out oslo.packaging
+* Fix two minor style things
+* Fixes #20. Make sure the manifest_maker monkeypatch only occurs once and that it gets all its required locals encapsulated. This is why I hate any sort of monkey-patching, but it appears to be the only way to get this feature working
+* Fix 'except as' statements that were breaking things in Python 2.5
+* Somehow neglected to include extra-file.txt used in the test for #15. Also moved the info log message that was causing othere tests to fail to only appear when commands are run that actually affect the manifest generation
+* Yet another Python 2.5 fix; I really ought to just set up my own 2.5 installation
+* multiprocessing doesn't exist in Python 2.5
+* Adds six.py and makes the necessary tweaks to improve support for using d2to1 natively across Python versions
+* Fix 'except as' statements that were breaking things in Python 2.5
+* Somehow neglected to include extra-file.txt used in the test for #15. Also moved the info log message that was causing othere tests to fail to only appear when commands are run that actually affect the manifest generation
+* Adds support for the tests_require keyword from setuptools. Tested by eating our own dogfood -- ./setup.py test now works for d2to1's own setup
+* support test suite and test tests-require
+* Patch manifest_maker to support extra_files--this is really the only way I can see to do this at all sensibly
+* This is a test that should really work to test this feature
+* openstack.common.setup: fails to get version from git
+* Use oslo-config-2013.1b3
+* Change the check for the existence of .git directory
+* returncode for Popen is valid only after communicate call
+* sort options to make --help output prettier
+* Allow running test in uninstalled source tree
+* Remove openstack.common.db.common
+* Add missing DBDuplicateEntry
+* Import sqlalchemy session/models/utils
+* Implements import_group
+* Fix Pep8 Warning
+* Fixes "is not", "not in" syntax usage
+* setup: count revs for revno if there are no tags
+* Use testtools as test base class
+* Move logging config options into the log module
+* Use revno and git sha for pre-release versioning
+* Add env var version override for packagers
+* trivial pep whitespace fix
+* Remove write_requirements
+* Rolling back to previous version of resolve_name; it's possible this can fail in the presence of a broken namespace package, but I want to make sure that's the case before trying to fix it
+* Stop using no longer valid -E option for pip
+* oops - now compatible with python 3
+* use regexp to parse the mailmap
+* Make tox run doctests
+* Verbose should not enable debug level logging
+* Fix pep8 E125 errors
+* Simplify version processing
+* Any exception that escapes from a hook gets reported along with a stack trace
+* Revert "Support lookup of value using "group.key""
+* remove debugging
+* Add a latex command to our sphinx builders
+* Support lookup of value using "group.key"
+* debugging auto builds
+* debugging auto builds
+* fix up importer; clean hook name before search
+* show traceback when a hook encounters an exception
+* Add deprecated --logdir common opt
+* Add deprecated --logfile common opt
+* Allow nova and others to override some logging defaults
+* Fixing the trim for ListOp when reading from config file
+* Fix set_default() with boolean CLI options
+* Rename utils.py to strutils.py
+* Improve cfg's argparse sub-parsers support
+* Make project pyflakes clean
+* Fix regression with cfg CLI arguments
+* Fix ListOpt to trim whitespace
+* Hide the GroupAttr conf and group attributes
+* Fix broken --help with CommonConfigOpts
+* updating sphinx documentation
+* Don't reference argparse._StoreAction
+* Fix minor coding style issue
+* Remove ConfigCliParser class
+* Add support for positional arguments
+* Use stock argparse behaviour for optional args
+* Use stock argparse --usage behaviour
+* Use stock argparse --version behaviour
+* Remove add_option() method
+* Completely remove cfg's disable_interspersed_args()
+* argparse support for cfg
+* Remove openstack.common.config and extensions
+* Fixes setup compatibility issue on Windows
+* Move utils.execute to its own module
+* Add a missing comma in a docstring
+* Import order clean-up
+* Fix a logic error in stable version calculation
+* cfg: fix required if option has a dash
+* Fix a couple of file handle leaks, using with statements
+* Ignore the stackforge jenkins email address
+* Extracted parse_host_port into network_utils
+* Add the rpc service and delete manager
+* Some changes I made weeks ago but forgot to commit
+* Added a method for parsing host:port pairs
+* Add basic periodic task infrastructure
+* Move manager.py and service.py into common
+* cfg: clean up None value handling
+* Allow set_default and set_override to use None
+* Tilde expansion for --config-file and --config-dir
+* Add multiple-driver support to the notifier api
+* Fix errors reported by pyflakes
+* Add import_opt() method to ConfigOpts
+* Remove unused imports
+* Modifies _is_opt_registered fcn to check for duplicate opts
+* fix bug lp:1019348,update openstack-common to support pep8 1.3
+* cfg: allow empty config values
+* Add SKIP_GENERATE_AUTHORS option to setup.py
+* Add SKIP_WRITE_GIT_CHANGELOG option to setup.py
+* Fix missing gettextutils in several modules
+* Fix up pre-versioning based on testing in glance
+* cfg: Fix typo in documentation
+* Fix mailmap
+* Split read_versioninfo into a method
+* Add common logging and notification
+* Add support for tag based versioning
+* Skip argparse when injecting requirements
+* Update common code to support pep 1.3. bug 1014216
+* Add autodoc generation to the build_sphinx command
+* Use 'is not None' instead of '!= None'
+* Adds support for bol and eol spaces to ini files
+* Add support to include config aliases
+* Create testutils with skip decorators
+* cfg: add a global CONF object
+* cfg: add generators for iterating over all options
+* cfg: move constructor args to __call__() args
+* Added support for proper bare URLs
+* Backslash continuation removal (common folsom-1)
+* Alphabetize imports in openstack/common/cfg.py
+* cfg: make reset() clear defaults and overrides
+* cfg: automatically create option groups
+* cfg: allow options to be marked as required
+* cfg: use a list comprehension instead of map()
+* Encapsulate common sdist actions into a cmdclass
+* Truly handle mailmap entries for all combinations
+* New ConfigOpts.find_file() for locating conf files
+* Handle authors existed before VCS was around
+* Support for directory source of config files
+* Provide file extension when when looking for files
+* Some refactoring of the cfg cache
+* Add caching to openstack.common.cfg
+* Add AUTHORS generation function
+* Change behavior in utils.import_object()
+* Move auth_str_equal() to a new authutils module
+* Create openstack.common.timeutils
+* Typofix, OptionGroup should be OptGroup
+* Use absolute import for iniparser
+* Make 'yes' also a true boolean
+* Finish implementing MultiStrOpt
+* Avoid leaking secrets into config logging
+* Add auth_str_equal() to common utils
+* Fix bug 954488
+* fix restructuredtext formatting in docstrings
+* Add ConfigOpts.print_help()
+* Fixes a bug where entry_points in a setup.py are blanked out if there are no entry points defined in setup.cfg
+* Promote more complete support for ISO 8601 time
+* cfg: fix a small comment typo
+* Several changes that I accidentally committed to d2to1's old repository. See the changes to the changelog for more details
+* cfg: unneeded multiple inheritance
+* PEP8 cleanup (openstack-common)
+* Backslash continuations (misc.)
+* Disable ConfigParser interpolation (lp#930270)
+* Add git changelog method
+* Add git vcsversion method
+* Updated tox config for multi-python testing
+* Split functions to avoid eventlet import
+* Implements blueprint separate-nova-volumeapi
+* Makes common/cfg.py raise AttributeError
+* PEP8 type comparison cleanup
+* Add the Mapping interface to cfg.ConfigOpts
+* Add support to cfg for disabling interspersed args
+* Add new cfg module
+* import should not return an instance of the class
+* use isinstance and types.*
+* make fix_path a real function so it can be mock'd
+* remove unused imports
+* merge in upstream
+* port execute and utcnow functions from nova
+* pull parse_mailmap str_dict_replace from nova
+* reog from import merge
+* Rajaram/Vinkesh|Default xmlns for extension serialization can be overriden, added default factory for extension middleware
+* Rajaram/Vinkesh | Copied tests for wsgi from nova. Added default content/accept types in Request which can be overridden by projects. Copied tests for XML serialization of Extension Controller's action from nova
+* Rajaram/Vinkesh | Fixed the extension bug where custom collection actions' routes in resource extension were not getting registered
+* Vinkesh/Rajaram|Added nova's extension framework into common and tests for it
+* Rajaram|renamed AppBaseException to OpenstackException and app_config_dir_name to config_dir as per jaypipes' feedback
+* Rajaram/Vinkesh | Removed references to Glance in code
+* Adding syslog support
+* Add some more generic middleware, request context, utils, and versioning. Add basic template for server binary
+* Initial skeleton project
--- /dev/null
+
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
--- /dev/null
+Metadata-Version: 1.1
+Name: pbr
+Version: 2.0.0
+Summary: Python Build Reasonableness
+Home-page: http://docs.openstack.org/developer/pbr/
+Author: OpenStack
+Author-email: openstack-dev@lists.openstack.org
+License: UNKNOWN
+Description: Introduction
+ ============
+
+ .. image:: https://img.shields.io/pypi/v/pbr.svg
+ :target: https://pypi.python.org/pypi/pbr/
+ :alt: Latest Version
+
+ .. image:: https://img.shields.io/pypi/dm/pbr.svg
+ :target: https://pypi.python.org/pypi/pbr/
+ :alt: Downloads
+
+ PBR is a library that injects some useful and sensible default behaviors
+ into your setuptools run. It started off life as the chunks of code that
+ were copied between all of the `OpenStack`_ projects. Around the time that
+ OpenStack hit 18 different projects each with at least 3 active branches,
+ it seemed like a good time to make that code into a proper reusable library.
+
+ PBR is only mildly configurable. The basic idea is that there's a decent
+ way to run things and if you do, you should reap the rewards, because then
+ it's simple and repeatable. If you want to do things differently, cool! But
+ you've already got the power of Python at your fingertips, so you don't
+ really need PBR.
+
+ PBR builds on top of the work that `d2to1`_ started to provide for declarative
+ configuration. `d2to1`_ is itself an implementation of the ideas behind
+ `distutils2`_. Although `distutils2`_ is now abandoned in favor of work towards
+ `PEP 426`_ and Metadata 2.0, declarative config is still a great idea and
+ specifically important in trying to distribute setup code as a library
+ when that library itself will alter how the setup is processed. As Metadata
+ 2.0 and other modern Python packaging PEPs come out, PBR aims to support
+ them as quickly as possible.
+
+ * License: Apache License, Version 2.0
+ * Documentation: http://docs.openstack.org/developer/pbr
+ * Source: http://git.openstack.org/cgit/openstack-dev/pbr
+ * Bugs: http://bugs.launchpad.net/pbr
+
+ .. _d2to1: https://pypi.python.org/pypi/d2to1
+ .. _distutils2: https://pypi.python.org/pypi/Distutils2
+ .. _PEP 426: http://legacy.python.org/dev/peps/pep-0426/
+ .. _OpenStack: https://www.openstack.org/
+
+
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Environment :: OpenStack
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Information Technology
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
--- /dev/null
+Introduction
+============
+
+.. image:: https://img.shields.io/pypi/v/pbr.svg
+ :target: https://pypi.python.org/pypi/pbr/
+ :alt: Latest Version
+
+.. image:: https://img.shields.io/pypi/dm/pbr.svg
+ :target: https://pypi.python.org/pypi/pbr/
+ :alt: Downloads
+
+PBR is a library that injects some useful and sensible default behaviors
+into your setuptools run. It started off life as the chunks of code that
+were copied between all of the `OpenStack`_ projects. Around the time that
+OpenStack hit 18 different projects each with at least 3 active branches,
+it seemed like a good time to make that code into a proper reusable library.
+
+PBR is only mildly configurable. The basic idea is that there's a decent
+way to run things and if you do, you should reap the rewards, because then
+it's simple and repeatable. If you want to do things differently, cool! But
+you've already got the power of Python at your fingertips, so you don't
+really need PBR.
+
+PBR builds on top of the work that `d2to1`_ started to provide for declarative
+configuration. `d2to1`_ is itself an implementation of the ideas behind
+`distutils2`_. Although `distutils2`_ is now abandoned in favor of work towards
+`PEP 426`_ and Metadata 2.0, declarative config is still a great idea and
+specifically important in trying to distribute setup code as a library
+when that library itself will alter how the setup is processed. As Metadata
+2.0 and other modern Python packaging PEPs come out, PBR aims to support
+them as quickly as possible.
+
+* License: Apache License, Version 2.0
+* Documentation: http://docs.openstack.org/developer/pbr
+* Source: http://git.openstack.org/cgit/openstack-dev/pbr
+* Bugs: http://bugs.launchpad.net/pbr
+
+.. _d2to1: https://pypi.python.org/pypi/d2to1
+.. _distutils2: https://pypi.python.org/pypi/Distutils2
+.. _PEP 426: http://legacy.python.org/dev/peps/pep-0426/
+.. _OpenStack: https://www.openstack.org/
--- /dev/null
+..
+ The name of this document and the anchor in this document must be
+ treated as a stable API. Links to this document are coded into
+ pbr and deployed versions of pbr will refer users to this document
+ in the case of certain errors.
+ Ensure any link you use in PBR is defined via a ref with .. _name.
+
+
+===================
+Compatibility Notes
+===================
+
+Useful notes about errors users may encounter when features cannot be
+supported on older versions of setuptools / pip / wheel.
+
+
+setuptools
+==========
+
+
+.. _evaluate-marker:
+
+evaluate_marker
+---------------
+
+evaluate_markers may run into issues with the '>', '>=', '<', and '<='
+operators if the installed version of setuptools is less than 17.1. Projects
+using these operators with markers should specify a minimum version of 17.1
+for setuptools.
+
+
+pip
+===
+
+markers
+-------
+
+For versions of pip < 7 with pbr < 1.9, dependencies that use markers will not
+be installed. Projects using pbr and markers should set a minimum version of
+1.9 for pbr.
+
+
+Recommended setup.py
+====================
+
+:ref:`setup_py`.
+
+
+Sphinx
+======
+
+.. _sphinx-1.5:
+
+Version 1.5.0+
+--------------
+
+The ``warning-is-error`` flag is only supported by Sphinx 1.5 and will cause
+errors when used with older versions.
--- /dev/null
+# -*- coding: utf-8 -*-
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('../..'))
+# -- General configuration ----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = ['sphinx.ext.autodoc',
+ 'oslosphinx']
+
+# autodoc generation is a bit aggressive and a nuisance when doing heavy
+# text edit cycles.
+# execute "export SPHINX_DEBUG=1" in your terminal to disable
+
+# Add any paths that contain templates here, relative to this directory.
+# templates_path = ['_templates']
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = 'pbr'
+copyright = '2013, OpenStack Foundation'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+add_module_names = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+exclude_trees = []
+
+# -- Options for HTML output --------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+html_theme = 'default'
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = '%sdoc' % project
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass
+# [howto/manual]).
+latex_documents = [
+ ('index',
+ '%s.tex' % project,
+ '%s Documentation' % project,
+ 'OpenStack Foundation', 'manual'),
+]
--- /dev/null
+=================================
+pbr - Python Build Reasonableness
+=================================
+
+A library for managing setuptools packaging needs in a consistent manner.
+
+`pbr` reads and then filters the `setup.cfg` data through a setup hook to
+fill in default values and provide more sensible behaviors, and then feeds
+the results in as the arguments to a call to `setup.py` - so the heavy
+lifting of handling python packaging needs is still being done by
+`setuptools`.
+
+Note that we don't support the `easy_install` aspects of setuptools: while
+we depend on setup_requires, for any install_requires we recommend that they
+be installed prior to running `setup.py install` - either by hand, or by using
+an install tool such as `pip`.
+
+What It Does
+============
+
+PBR can and does do a bunch of things for you:
+
+* **Version**: Manage version number based on git revisions and tags
+* **AUTHORS**: Generate AUTHORS file from git log
+* **ChangeLog**: Generate ChangeLog from git log
+* **Manifest**: Generate a sensible manifest from git files and some standard
+ files
+* **Sphinx Autodoc**: Generate autodoc stub files for your whole module
+* **Requirements**: Store your dependencies in a pip requirements file
+* **long_description**: Use your README file as a long_description
+* **Smart find_packages**: Smartly find packages under your root package
+
+Version
+-------
+
+Versions can be managed two ways - postversioning and preversioning.
+Postversioning is the default, and preversioning is enabled by setting
+``version`` in the setup.cfg ``metadata`` section. In both cases version
+strings are inferred from git.
+
+If the currently checked out revision is tagged, that tag is used as
+the version.
+
+If the currently checked out revision is not tagged, then we take the
+last tagged version number and increment it to get a minimum target
+version.
+
+We then walk git history back to the last release. Within each commit we look
+for a Sem-Ver: pseudo header, and if found parse it looking for keywords.
+Unknown symbols are not an error (so that folk can't wedge pbr or break their
+tree), but we will emit an info level warning message. Known symbols:
+``feature``, ``api-break``, ``deprecation``, ``bugfix``. A missing
+Sem-Ver line is equivalent to ``Sem-Ver: bugfix``. The ``bugfix`` symbol causes
+a patch level increment to the version. The ``feature`` and ``deprecation``
+symbols cause a minor version increment. The ``api-break`` symbol causes a
+major version increment.
+
+If postversioning is in use, we use the resulting version number as the target
+version.
+
+If preversioning is in use we check that the version set in the metadata
+section of `setup.cfg` is greater than the version we infer using the above
+method. If the inferred version is greater than the preversioning value we
+raise an error, otherwise we use the version from `setup.cfg` as the target.
+
+We then generate dev version strings based on the commits since the last
+release and include the current git sha to disambiguate multiple dev versions
+with the same number of commits since the release.
+
+.. note::
+
+ `pbr` expects git tags to be signed for use in calculating versions
+
+The versions are expected to be compliant with :doc:`semver`.
+
+The ``version.SemanticVersion`` class can be used to query versions of a
+package and present it in various forms - ``debian_version()``,
+``release_string()``, ``rpm_string()``, ``version_string()``, or
+``version_tuple()``.
+
+AUTHORS and ChangeLog
+---------------------
+
+Why keep an `AUTHORS` or a `ChangeLog` file when git already has all of the
+information you need? `AUTHORS` generation supports filtering/combining based
+on a standard `.mailmap` file.
+
+Manifest
+--------
+
+Just like `AUTHORS` and `ChangeLog`, why keep a list of files you wish to
+include when you can find many of these in git. `MANIFEST.in` generation
+ensures almost all files stored in git, with the exception of `.gitignore`,
+`.gitreview` and `.pyc` files, are automatically included in your
+distribution. In addition, the generated `AUTHORS` and `ChangeLog` files are
+also included. In many cases, this removes the need for an explicit
+'MANIFEST.in' file
+
+Sphinx Autodoc
+--------------
+
+Sphinx can produce auto documentation indexes based on signatures and
+docstrings of your project but you have to give it index files to tell it
+to autodoc each module: that's kind of repetitive and boring. PBR will scan
+your project, find all of your modules, and generate all of the stub files for
+you.
+
+Sphinx documentation setups are altered to generate man pages by default. They
+also have several pieces of information that are known to setup.py injected
+into the sphinx config.
+
+See the pbr_ section for details on configuring your project for autodoc.
+
+Requirements
+------------
+
+You may not have noticed, but there are differences in how pip
+`requirements.txt` files work and how distutils wants to be told about
+requirements. The pip way is nicer because it sure does make it easier to
+populate a virtualenv for testing or to just install everything you need.
+Duplicating the information, though, is super lame. To solve this issue, `pbr`
+will let you use `requirements.txt`-format files to describe the requirements
+for your project and will then parse these files, split them up appropriately,
+and inject them into the `install_requires`, `tests_require` and/or
+`dependency_links` arguments to `setup`. Voila!
+
+You can also have a requirement file for each specific major version of Python.
+If you want to have a different package list for Python 3 then just drop a
+`requirements-py3.txt` and it will be used instead.
+
+Finally, it is possible to specify groups of optional dependencies, or
+`"extra" requirements`_, in your `setup.cfg` rather than `setup.py`.
+
+long_description
+----------------
+
+There is no need to maintain two long descriptions- and your README file is
+probably a good long_description. So we'll just inject the contents of your
+README.rst, README.txt or README file into your empty long_description. Yay
+for you.
+
+Usage
+=====
+
+`pbr` is a setuptools plugin and so to use it you must use setuptools and call
+``setuptools.setup()``. While the normal setuptools facilities are available,
+pbr makes it possible to express them through static data files.
+
+.. _setup_py:
+
+setup.py
+--------
+
+`pbr` only requires a minimal `setup.py` file compared to a standard setuptools
+project. This is because most configuration is located in static configuration
+files. This recommended minimal `setup.py` file should look something like this::
+
+ #!/usr/bin/env python
+
+ from setuptools import setup
+
+ setup(
+ setup_requires=['pbr>=1.9', 'setuptools>=17.1'],
+ pbr=True,
+ )
+
+.. note::
+
+ It is necessary to specify ``pbr=True`` to enabled `pbr` functionality.
+
+.. note::
+
+ While one can pass any arguments supported by setuptools to `setup()`,
+ any conflicting arguments supplied in `setup.cfg` will take precedence.
+
+setup.cfg
+---------
+
+The `setup.cfg` file is an ini-like file that can mostly replace the `setup.py`
+file. It is based on the distutils2_ `setup.cfg` file. A simple sample can be
+found in `pbr`'s own `setup.cfg` (it uses its own machinery to install
+itself)::
+
+ [metadata]
+ name = pbr
+ author = OpenStack Foundation
+ author-email = openstack-dev@lists.openstack.org
+ summary = OpenStack's setup automation in a reusable form
+ description-file = README
+ home-page = https://launchpad.net/pbr
+ license = Apache-2
+ classifier =
+ Development Status :: 4 - Beta
+ Environment :: Console
+ Environment :: OpenStack
+ Intended Audience :: Developers
+ Intended Audience :: Information Technology
+ License :: OSI Approved :: Apache Software License
+ Operating System :: OS Independent
+ Programming Language :: Python
+ keywords =
+ setup
+ distutils
+ [files]
+ packages =
+ pbr
+ data_files =
+ etc/pbr = etc/*
+ etc/init =
+ pbr.packaging.conf
+ pbr.version.conf
+ [entry_points]
+ console_scripts =
+ pbr = pbr.cmd:main
+ pbr.config.drivers =
+ plain = pbr.cfg.driver:Plain
+
+pbr provides its own section in these documents, ostensibly called `pbr`. Most
+other sections are provided by setuptools and may influence either the build
+itself or the output of various `setuptools commands`__. The remaining sections
+are provided by libraries that provide setuptools extensions, such as
+`extract_mesages` (provided by `Babel`__) or `sphinx_build` (provided by
+`Sphinx`__). Some of these are described below.
+
+__ https://setuptools.readthedocs.io/en/latest/setuptools.html#command-reference
+__ http://babel.pocoo.org/en/latest/setup.html
+__ http://www.sphinx-doc.org/en/stable/setuptools.html
+
+.. note::
+
+ Comments may be used in `setup.cfg`, however all comments should start with
+ a `#` and may be on a single line, or in line, with at least one white space
+ character immediately preceding the `#`. Semicolons are not a supported
+ comment delimiter. For instance::
+
+ [section]
+ # A comment at the start of a dedicated line
+ key =
+ value1 # An in line comment
+ value2
+ # A comment on a dedicated line
+ value3
+
+files
+~~~~~
+
+The ``files`` section defines the install location of files in the package
+using three fundamental keys: ``packages``, ``namespace_packages``, and
+``data_files``.
+
+``packages`` is a list of top-level packages that should be installed. The
+behavior of packages is similar to ``setuptools.find_packages`` in that it
+recurses the python package hierarchy below the given top level and installs
+all of it. If ``packages`` is not specified, it defaults to the value of the
+``name`` field given in the ``[metadata]`` section.
+
+``namespace_packages`` is the same, but is a list of packages that provide
+namespace packages.
+
+``data_files`` lists files to be installed. The format is an indented block
+that contains key value pairs which specify target directory and source file
+to install there. More than one source file for a directory may be indicated
+with a further indented list. Source files are stripped of leading directories.
+Additionally, `pbr` supports a simple file globbing syntax for installing
+entire directory structures, thus::
+
+ [files]
+ data_files =
+ etc/pbr = etc/pbr/*
+ etc/neutron =
+ etc/api-paste.ini
+ etc/dhcp-agent.ini
+ etc/init.d = neutron.init
+
+will result in `/etc/neutron` containing `api-paste.ini` and `dhcp-agent.ini`,
+both of which pbr will expect to find in the `etc` directory in the root of
+the source tree. Additionally, `neutron.init` from that dir will be installed
+in `/etc/init.d`. All of the files and directories located under `etc/pbr` in
+the source tree will be installed into `/etc/pbr`.
+
+Note that this behavior is relative to the effective root of the environment
+into which the packages are installed, so depending on available permissions
+this could be the actual system-wide `/etc` directory or just a top-level `etc`
+subdirectory of a virtualenv.
+
+pbr
+~~~
+
+The ``pbr`` section controls pbr specific options and behaviours.
+
+The ``autodoc_tree_index_modules`` is a boolean option controlling whether pbr
+should generate an index of modules using ``sphinx-apidoc``. By default,
+`setup.py` is excluded. The list of excluded modules can be specified with the
+``autodoc_tree_excludes`` option. See the `sphinx-apidoc man page`_ for more
+information.
+
+The ``autodoc_index_modules`` is a boolean option controlling whether `pbr`
+should itself generates documentation for Python modules of the project. By
+default, all found Python modules are included; some of them can be excluded
+by listing them in ``autodoc_exclude_modules``. This list of modules can
+contains `fnmatch` style pattern (e.g. `myapp.tests.*`) to exclude some modules.
+
+.. note::
+
+ When using ``autodoc_tree_excludes`` or ``autodoc_index_modules`` you may
+ also need to set ``exclude_patterns`` in your Sphinx configuration file
+ (generally found at `doc/source/conf.py` in most OpenStack projects)
+ otherwise Sphinx may complain about documents that are not in a toctree.
+ This is especially true if the ``[sphinx_build] warning-is-error`` option is
+ set. See the `Sphinx build configuration file`_ documentation for more
+ information on configuring Sphinx.
+
+entry_points
+~~~~~~~~~~~~
+
+The ``entry_points`` section defines entry points for generated console scripts
+and python libraries. This is actually provided by `setuptools`__ but is
+documented here owing to its importance.
+
+The general syntax of specifying entry points is a top level name indicating
+the entry point group name, followed by one or more key value pairs naming
+the entry point to be installed. For instance::
+
+ [entry_points]
+ console_scripts =
+ pbr = pbr.cmd:main
+ pbr.config.drivers =
+ plain = pbr.cfg.driver:Plain
+ fancy = pbr.cfg.driver:Fancy
+
+Will cause a console script called `pbr` to be installed that executes the
+`main` function found in `pbr.cmd`. Additionally, two entry points will be
+installed for `pbr.config.drivers`, one called `plain` which maps to the
+`Plain` class in `pbr.cfg.driver` and one called `fancy` which maps to the
+`Fancy` class in `pbr.cfg.driver`.
+
+__ https://setuptools.readthedocs.io/en/latest/setuptools.html#options
+
+Requirements
+------------
+
+Requirement files should be given one of the below names. This order is also
+the order that the requirements are tried in (where `N` is the Python major
+version number used to install the package):
+
+* requirements-pyN.txt
+* tools/pip-requires-py3
+* requirements.txt
+* tools/pip-requires
+
+Only the first file found is used to install the list of packages it contains.
+
+.. note::
+
+ The 'requirements-pyN.txt' file is deprecated - 'requirements.txt' should
+ be universal. You can use `Environment markers`_ for this purpose.
+
+Extra requirements
+~~~~~~~~~~~~~~~~~~
+
+Groups of optional dependencies, or `"extra" requirements`_, can be described
+in your `setup.cfg`, rather than needing to be added to `setup.py`. An example
+(which also demonstrates the use of environment markers) is shown below.
+
+Environment markers
+~~~~~~~~~~~~~~~~~~~
+
+Environment markers are `conditional dependencies`_ which can be added to the
+requirements (or to a group of extra requirements) automatically, depending
+on the environment the installer is running in. They can be added to
+requirements in the requirements file, or to extras defined in `setup.cfg`,
+but the format is slightly different for each.
+
+For ``requirements.txt``::
+
+ argparse; python_version=='2.6'
+
+This will result in the package depending on ``argparse`` only if it's being
+installed into Python 2.6
+
+For extras specified in `setup.cfg`, add an ``extras`` section. For instance,
+to create two groups of extra requirements with additional constraints on the
+environment, you can use::
+
+ [extras]
+ security =
+ aleph
+ bet:python_version=='3.2'
+ gimel:python_version=='2.7'
+ testing =
+ quux:python_version=='2.7'
+
+Additional Docs
+===============
+
+.. toctree::
+ :maxdepth: 1
+
+ packagers
+ semver
+ testing
+ compatibility
+ api/modules
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
+.. _"extra" requirements:
+ https://www.python.org/dev/peps/pep-0426/#extras-optional-dependencies
+.. _conditional dependencies:
+ https://www.python.org/dev/peps/pep-0426/#environment-markers
+.. _distutils2: http://alexis.notmyidea.org/distutils2/setupcfg.html
+.. _sphinx-apidoc man page: http://sphinx-doc.org/man/sphinx-apidoc.html
+.. _Sphinx build configuration file: http://sphinx-doc.org/config.html
--- /dev/null
+===============================
+ Notes for Package maintainers
+===============================
+
+If you are maintaining packages of software that uses `pbr`, there are some
+features you probably want to be aware of that can make your life easier.
+They are exposed by environment variables, so adding them to rules or spec
+files should be fairly easy.
+
+Versioning
+==========
+
+`pbr`, when run in a git repo, derives the version of a package from the
+git tags. When run in a tarball with a proper egg-info dir, it will happily
+pull the version from that. So for the most part, the package maintainers
+shouldn't need to care. However, if you are doing something like keeping a
+git repo with the sources and the packaging intermixed and it's causing pbr
+to get confused about whether its in its own git repo or not, you can set
+`PBR_VERSION`:
+
+::
+
+ PBR_VERSION=1.2.3
+
+and all version calculation logic will be completely skipped and the supplied
+version will be considered absolute.
+
+Distribution version numbers
+============================
+
+`pbr` will automatically calculate upstream version numbers for dpkg and rpm
+using systems. Releases are easy (and obvious). When packaging preleases though
+things get more complex. Firstly, semver does not provide for any sort order
+between pre-releases and development snapshots, so it can be complex (perhaps
+intractable) to package both into one repository - we recommend with either
+packaging pre-release releases (alpha/beta/rc's) or dev snapshots but not both.
+Secondly, as pre-releases and snapshots have the same major/minor/patch version
+as the version they lead up to, but have to sort before it, we cannot map their
+version naturally into the rpm version namespace: instead we represent their
+versions as versions of the release before.
+
+Dependencies
+============
+
+As of 1.0.0 `pbr` doesn't alter the dependency behaviour of `setuptools`.
+
+Older versions would invoke `pip` internally under some circumstances and
+required the environment variable `SKIP_PIP_INSTALL` to be set to prevent
+that. Since 1.0.0 we now document that dependencies should be installed before
+installing a `pbr` using package. We don't support easy install, but neither
+do we interfere with it today. If you observe easy install being triggered when
+building a binary package, then you've probably missed one or more package
+requirements.
+
+Note: we reserve the right to disable easy install via `pbr` in future, since
+we don't want to debug or support the interactions that can occur when using
+it.
+
+Tarballs
+========
+
+`pbr` includes everything in a source tarball that is in the original `git`
+repository. This can again cause havoc if a package maintainer is doing fancy
+things with combined `git` repos, and is generating a source tarball using
+`python setup.py sdist` from that repo. If that is the workflow the packager
+is using, setting `SKIP_GIT_SDIST`:
+
+::
+
+ SKIP_GIT_SDIST=1
+
+will cause all logic around using git to find the files that should be in the
+source tarball to be skipped. Beware though, that because `pbr` packages
+automatically find all of the files, most of them do not have a complete
+`MANIFEST.in` file, so its possible that a tarball produced in that way will
+be missing files.
+
+AUTHORS and ChangeLog
+=====================
+
+`pbr` generates AUTHORS and ChangeLog files from git information. This
+can cause problem in distro packaging if package maintainer is using git
+repository for packaging source. If that is the case setting
+`SKIP_GENERATE_AUTHORS`
+
+::
+
+ SKIP_GENERATE_AUTHORS=1
+
+will cause logic around generating AUTHORS using git information to be
+skipped. Similarly setting `SKIP_WRITE_GIT_CHANGELOG`
+
+::
+
+ SKIP_WRITE_GIT_CHANGELOG=1
+
+will cause logic around generating ChangeLog file using git
+information to be skipped.
--- /dev/null
+Linux/Python Compatible Semantic Versioning 3.0.0
+=================================================
+
+This is a fork of Semantic Versioning 2.0. The specific changes have to do
+with the format of pre-release and build labels, specifically to make them
+not confusing when co-existing with Linux distribution packaging and Python
+packaging. Inspiration for the format of the pre-release and build labels
+came from Python's PEP440.
+
+Changes vs SemVer 2.0
+---------------------
+
+#. dev versions are defined. These are extremely useful when
+ dealing with CI and CD systems when 'every commit is a release' is not
+ feasible.
+
+#. All versions have been made PEP-440 compatible, because of our deep
+ roots in Python. Pre-release versions are now separated by . not -, and
+ use a/b/c rather than alpha/beta etc.
+
+Summary
+-------
+
+Given a version number MAJOR.MINOR.PATCH,
+increment the:
+
+#. MAJOR version when you make incompatible API changes,
+#. MINOR version when you add functionality in a backwards-compatible
+ manner, and
+#. PATCH version when you make backwards-compatible bug fixes.
+
+Introduction
+------------
+
+In the world of software management there exists a dread place called
+"dependency hell." The bigger your system grows and the more packages
+you integrate into your software, the more likely you are to find
+yourself, one day, in this pit of despair.
+
+In systems with many dependencies, releasing new package versions can
+quickly become a nightmare. If the dependency specifications are too
+tight, you are in danger of version lock (the inability to upgrade a
+package without having to release new versions of every dependent
+package). If dependencies are specified too loosely, you will inevitably
+be bitten by version promiscuity (assuming compatibility with more
+future versions than is reasonable). Dependency hell is where you are
+when version lock and/or version promiscuity prevent you from easily and
+safely moving your project forward.
+
+As a solution to this problem, I propose a simple set of rules and
+requirements that dictate how version numbers are assigned and
+incremented. These rules are based on but not necessarily limited to
+pre-existing widespread common practices in use in both closed and
+open-source software. For this system to work, you first need to declare
+a public API. This may consist of documentation or be enforced by the
+code itself. Regardless, it is important that this API be clear and
+precise. Once you identify your public API, you communicate changes to
+it with specific increments to your version number. Consider a version
+format of X.Y.Z (Major.Minor.Patch). Bug fixes not affecting the API
+increment the patch version, backwards compatible API additions/changes
+increment the minor version, and backwards incompatible API changes
+increment the major version.
+
+I call this system "Semantic Versioning." Under this scheme, version
+numbers and the way they change convey meaning about the underlying code
+and what has been modified from one version to the next.
+
+Semantic Versioning Specification (SemVer)
+------------------------------------------
+
+The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT",
+"SHOULD", "SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this
+document are to be interpreted as described in `RFC
+2119 <http://tools.ietf.org/html/rfc2119>`__.
+
+#. Software using Semantic Versioning MUST declare a public API. This
+ API could be declared in the code itself or exist strictly in
+ documentation. However it is done, it should be precise and
+ comprehensive.
+
+#. A normal version number MUST take the form X.Y.Z where X, Y, and Z
+ are non-negative integers, and MUST NOT contain leading zeroes. X is
+ the major version, Y is the minor version, and Z is the patch
+ version. Each element MUST increase numerically. For instance: 1.9.0
+ -> 1.10.0 -> 1.11.0.
+
+#. Once a versioned package has been released, the contents of that
+ version MUST NOT be modified. Any modifications MUST be released as
+ a new version.
+
+#. Major version zero (0.y.z) is for initial development. Anything may
+ change at any time. The public API should not be considered stable.
+
+#. Version 1.0.0 defines the public API. The way in which the version
+ number is incremented after this release is dependent on this public
+ API and how it changes.
+
+#. Patch version Z (x.y.Z \| x > 0) MUST be incremented if only
+ backwards compatible bug fixes are introduced. A bug fix is defined
+ as an internal change that fixes incorrect behavior.
+
+#. Minor version Y (x.Y.z \| x > 0) MUST be incremented if new,
+ backwards compatible functionality is introduced to the public API.
+ It MUST be incremented if any public API functionality is marked as
+ deprecated. It MAY be incremented if substantial new functionality
+ or improvements are introduced within the private code. It MAY
+ include patch level changes. Patch version MUST be reset to 0 when
+ minor version is incremented.
+
+#. Major version X (X.y.z \| X > 0) MUST be incremented if any
+ backwards incompatible changes are introduced to the public API. It
+ MAY also include minor and patch level changes. Patch and minor
+ version MUST be reset to 0 when major version is incremented.
+
+#. A pre-release version MAY be denoted by appending a dot
+ separated identifier immediately following the patch version.
+ The identifier MUST comprise only a, b, c followed by non-negative
+ integer value. The identifier MUST NOT be empty.
+ Pre-release versions have a lower precedence than the associated normal
+ version. A pre-release version indicates that
+ the version is unstable and might not satisfy the intended
+ compatibility requirements as denoted by its associated normal
+ version. Examples: 1.0.0.a1, 1.0.0.b99, 1.0.0.c1000.
+
+#. A development version MAY be denoted by appending a dot separated
+ identifier immediately following the patch version.
+ The identifier MUST comprise the string dev followed by non-negative
+ integer value. The identifier MUST NOT be empty. Development versions
+ have a lower precedence than the associated normal version or pre-release
+ version. A development version is a completely unsupported and conveys no
+ API promises when related to other versions. They are more useful as
+ communication vehicles between developers of a community, whereas
+ pre-releases, while potentially prone to break still, are intended for
+ externally facing communication of not-yet-released ideas. Dev versions
+ are not public artifacts and should never be placed in public
+ repositories: they are intended as developer-local resources. Examples:
+ 1.0.0.dev1, 1.0.0.a1.dev1
+
+#. git version metadata MAY be denoted by appending a dot separated
+ identifier immediately following a development or pre-release version.
+ The identifier MUST comprise the character g followed by a seven
+ character git short-sha. The sha MUST NOT be empty. git version
+ metadata MUST be ignored when determining version precedence. Thus
+ two versions that differ only in the git version, have the same
+ precedence. Example: 1.0.0.a1.g95a9beb.
+
+#. Build metadata MAY be denoted by appending a plus sign and a series
+ of dot separated identifiers immediately following the patch or
+ pre-release version. Identifiers MUST comprise only ASCII
+ alphanumerics [0-9A-Za-z]. Identifiers MUST NOT be empty. Build
+ metadata MUST be ignored when determining version precedence. Thus
+ two versions that differ only in the build metadata, have the same
+ precedence. Examples: 1.0.0.a1+001, 1.0.0+20130313144700,
+ 1.0.0.b1+exp.sha.5114f85.
+
+#. Precedence refers to how versions are compared to each other when
+ ordered. Precedence MUST be calculated by separating the version
+ into major, minor, patch, pre-release, and development identifiers in
+ that order (Build metadata does not figure into precedence). Precedence
+ is determined by the first difference when comparing each of these
+ identifiers from left to right as follows: Major, minor, and patch
+ versions are always compared numerically. Example: 1.0.0 < 2.0.0 <
+ 2.1.0 < 2.1.1. When major, minor, and patch are equal, a pre-release
+ version has lower precedence than a normal version. Example:
+ 1.0.0.a1 < 1.0.0. When major, minor, patch and pre-release are equal, a
+ development version has a lower precedence than a normal version and of a
+ pre-release version. Example: 1.0.0.dev1 < 1.0.0 and 1.0.0.dev9 <
+ 1.0.0.a1 and 1.0.0.a1 < 1.0.0.a2.dev4. Precedence for two pre-release
+ versions with the same major, minor, and patch version MUST be determined
+ by comparing the identifier to the right of the patch version as follows:
+ if the alpha portion matches, the numeric portion is compared in
+ numerical sort order. If the alpha portion does not match, the sort order
+ is dev < a < b < c. Example: 1.0.0.dev8 < 1.0.0.dev9 < 1.0.0.a1.dev3 <
+ 1.0.0.a1 < 1.0.0.b2 < 1.0.0.c1 < 1.0.0. Precedence for dev versions if
+ all other components are equal is done by comparing their numeric
+ component. If all other components are not equal, predence is determined
+ by comparing the other components.
+
+Why Use Semantic Versioning?
+----------------------------
+
+This is not a new or revolutionary idea. In fact, you probably do
+something close to this already. The problem is that "close" isn't good
+enough. Without compliance to some sort of formal specification, version
+numbers are essentially useless for dependency management. By giving a
+name and clear definition to the above ideas, it becomes easy to
+communicate your intentions to the users of your software. Once these
+intentions are clear, flexible (but not too flexible) dependency
+specifications can finally be made.
+
+A simple example will demonstrate how Semantic Versioning can make
+dependency hell a thing of the past. Consider a library called
+"Firetruck." It requires a Semantically Versioned package named
+"Ladder." At the time that Firetruck is created, Ladder is at version
+3.1.0. Since Firetruck uses some functionality that was first introduced
+in 3.1.0, you can safely specify the Ladder dependency as greater than
+or equal to 3.1.0 but less than 4.0.0. Now, when Ladder version 3.1.1
+and 3.2.0 become available, you can release them to your package
+management system and know that they will be compatible with existing
+dependent software.
+
+As a responsible developer you will, of course, want to verify that any
+package upgrades function as advertised. The real world is a messy
+place; there's nothing we can do about that but be vigilant. What you
+can do is let Semantic Versioning provide you with a sane way to release
+and upgrade packages without having to roll new versions of dependent
+packages, saving you time and hassle.
+
+If all of this sounds desirable, all you need to do to start using
+Semantic Versioning is to declare that you are doing so and then follow
+the rules. Link to this website from your README so others know the
+rules and can benefit from them.
+
+FAQ
+---
+
+How should I deal with revisions in the 0.y.z initial development phase?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+The simplest thing to do is start your initial development release at
+0.1.0 and then increment the minor version for each subsequent release.
+
+How do I know when to release 1.0.0?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+If your software is being used in production, it should probably already
+be 1.0.0. If you have a stable API on which users have come to depend,
+you should be 1.0.0. If you're worrying a lot about backwards
+compatibility, you should probably already be 1.0.0.
+
+Doesn't this discourage rapid development and fast iteration?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Major version zero is all about rapid development. If you're changing
+the API every day you should either still be in version 0.y.z or on a
+separate development branch working on the next major version.
+
+If even the tiniest backwards incompatible changes to the public API require a major version bump, won't I end up at version 42.0.0 very rapidly?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+This is a question of responsible development and foresight.
+Incompatible changes should not be introduced lightly to software that
+has a lot of dependent code. The cost that must be incurred to upgrade
+can be significant. Having to bump major versions to release
+incompatible changes means you'll think through the impact of your
+changes, and evaluate the cost/benefit ratio involved.
+
+Documenting the entire public API is too much work!
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+It is your responsibility as a professional developer to properly
+document software that is intended for use by others. Managing software
+complexity is a hugely important part of keeping a project efficient,
+and that's hard to do if nobody knows how to use your software, or what
+methods are safe to call. In the long run, Semantic Versioning, and the
+insistence on a well defined public API can keep everyone and everything
+running smoothly.
+
+What do I do if I accidentally release a backwards incompatible change as a minor version?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+As soon as you realize that you've broken the Semantic Versioning spec,
+fix the problem and release a new minor version that corrects the
+problem and restores backwards compatibility. Even under this
+circumstance, it is unacceptable to modify versioned releases. If it's
+appropriate, document the offending version and inform your users of the
+problem so that they are aware of the offending version.
+
+What should I do if I update my own dependencies without changing the public API?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+That would be considered compatible since it does not affect the public
+API. Software that explicitly depends on the same dependencies as your
+package should have their own dependency specifications and the author
+will notice any conflicts. Determining whether the change is a patch
+level or minor level modification depends on whether you updated your
+dependencies in order to fix a bug or introduce new functionality. I
+would usually expect additional code for the latter instance, in which
+case it's obviously a minor level increment.
+
+What if I inadvertently alter the public API in a way that is not compliant with the version number change (i.e. the code incorrectly introduces a major breaking change in a patch release)
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Use your best judgment. If you have a huge audience that will be
+drastically impacted by changing the behavior back to what the public
+API intended, then it may be best to perform a major version release,
+even though the fix could strictly be considered a patch release.
+Remember, Semantic Versioning is all about conveying meaning by how the
+version number changes. If these changes are important to your users,
+use the version number to inform them.
+
+How should I handle deprecating functionality?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+Deprecating existing functionality is a normal part of software
+development and is often required to make forward progress. When you
+deprecate part of your public API, you should do two things: (1) update
+your documentation to let users know about the change, (2) issue a new
+minor release with the deprecation in place. Before you completely
+remove the functionality in a new major release there should be at least
+one minor release that contains the deprecation so that users can
+smoothly transition to the new API.
+
+Does SemVer have a size limit on the version string?
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+No, but use good judgment. A 255 character version string is probably
+overkill, for example. Also, specific systems may impose their own
+limits on the size of the string.
+
+About
+-----
+
+The Linux/Python Compatible Semantic Versioning specification is maintained
+by the `OpenStack <http://openstack.org>`_ project.
+
+It is based on The Semantic Versioning specification, which was
+authored by `Tom Preston-Werner <http://tom.preston-werner.com>`__,
+with inputs from `PEP 440 <http://www.python.org/dev/peps/pep-0440/>`_
+
+If you'd like to leave feedback, please `open an issue
+<https://bugs.launchpad.net/pbr/+filebug>`_.
+
+License
+-------
+
+Creative Commons - CC BY 3.0 http://creativecommons.org/licenses/by/3.0/
--- /dev/null
+Running the Tests for pbr
+=========================
+
+The testing system is based on a combination of `tox`_ and `testr`_. The canonical
+approach to running tests is to simply run the command ``tox``. This will
+create virtual environments, populate them with dependencies and run all of
+the tests that OpenStack CI systems run. Behind the scenes, tox is running
+``testr run --parallel``, but is set up such that you can supply any additional
+testr arguments that are needed to tox. For example, you can run:
+``tox -- --analyze-isolation`` to cause tox to tell testr to add
+``--analyze-isolation`` to its argument list.
+
+It is also possible to run the tests inside of a virtual environment
+you have created, or it is possible that you have all of the dependencies
+installed locally already. If you'd like to go this route, the requirements
+are listed in ``requirements.txt`` and the requirements for testing are in
+``test-requirements.txt``. Installing them via pip, for instance, is simply::
+
+ pip install -r requirements.txt -r test-requirements.txt
+
+In you go this route, you can interact with the testr command directly.
+Running ``testr run`` will run the entire test suite. ``testr run --parallel``
+will run it in parallel (this is the default incantation tox uses). More
+information about testr can be found at: http://wiki.openstack.org/testr
+
+.. _tox: http://tox.testrun.org/
+.. _testr: https://wiki.openstack.org/wiki/Testr
--- /dev/null
+Metadata-Version: 1.1
+Name: pbr
+Version: 2.0.0
+Summary: Python Build Reasonableness
+Home-page: http://docs.openstack.org/developer/pbr/
+Author: OpenStack
+Author-email: openstack-dev@lists.openstack.org
+License: UNKNOWN
+Description: Introduction
+ ============
+
+ .. image:: https://img.shields.io/pypi/v/pbr.svg
+ :target: https://pypi.python.org/pypi/pbr/
+ :alt: Latest Version
+
+ .. image:: https://img.shields.io/pypi/dm/pbr.svg
+ :target: https://pypi.python.org/pypi/pbr/
+ :alt: Downloads
+
+ PBR is a library that injects some useful and sensible default behaviors
+ into your setuptools run. It started off life as the chunks of code that
+ were copied between all of the `OpenStack`_ projects. Around the time that
+ OpenStack hit 18 different projects each with at least 3 active branches,
+ it seemed like a good time to make that code into a proper reusable library.
+
+ PBR is only mildly configurable. The basic idea is that there's a decent
+ way to run things and if you do, you should reap the rewards, because then
+ it's simple and repeatable. If you want to do things differently, cool! But
+ you've already got the power of Python at your fingertips, so you don't
+ really need PBR.
+
+ PBR builds on top of the work that `d2to1`_ started to provide for declarative
+ configuration. `d2to1`_ is itself an implementation of the ideas behind
+ `distutils2`_. Although `distutils2`_ is now abandoned in favor of work towards
+ `PEP 426`_ and Metadata 2.0, declarative config is still a great idea and
+ specifically important in trying to distribute setup code as a library
+ when that library itself will alter how the setup is processed. As Metadata
+ 2.0 and other modern Python packaging PEPs come out, PBR aims to support
+ them as quickly as possible.
+
+ * License: Apache License, Version 2.0
+ * Documentation: http://docs.openstack.org/developer/pbr
+ * Source: http://git.openstack.org/cgit/openstack-dev/pbr
+ * Bugs: http://bugs.launchpad.net/pbr
+
+ .. _d2to1: https://pypi.python.org/pypi/d2to1
+ .. _distutils2: https://pypi.python.org/pypi/Distutils2
+ .. _PEP 426: http://legacy.python.org/dev/peps/pep-0426/
+ .. _OpenStack: https://www.openstack.org/
+
+
+Platform: UNKNOWN
+Classifier: Development Status :: 5 - Production/Stable
+Classifier: Environment :: Console
+Classifier: Environment :: OpenStack
+Classifier: Intended Audience :: Developers
+Classifier: Intended Audience :: Information Technology
+Classifier: License :: OSI Approved :: Apache Software License
+Classifier: Operating System :: OS Independent
+Classifier: Programming Language :: Python
+Classifier: Programming Language :: Python :: 2
+Classifier: Programming Language :: Python :: 2.6
+Classifier: Programming Language :: Python :: 2.7
+Classifier: Programming Language :: Python :: 3
+Classifier: Programming Language :: Python :: 3.3
+Classifier: Programming Language :: Python :: 3.4
+Classifier: Programming Language :: Python :: 3.5
--- /dev/null
+.coveragerc
+.mailmap
+.testr.conf
+AUTHORS
+CONTRIBUTING.rst
+ChangeLog
+LICENSE
+README.rst
+setup.cfg
+setup.py
+test-requirements.txt
+tox.ini
+doc/source/compatibility.rst
+doc/source/conf.py
+doc/source/index.rst
+doc/source/packagers.rst
+doc/source/semver.rst
+doc/source/testing.rst
+pbr/__init__.py
+pbr/builddoc.py
+pbr/core.py
+pbr/extra_files.py
+pbr/find_package.py
+pbr/git.py
+pbr/options.py
+pbr/packaging.py
+pbr/pbr_json.py
+pbr/testr_command.py
+pbr/util.py
+pbr/version.py
+pbr.egg-info/PKG-INFO
+pbr.egg-info/SOURCES.txt
+pbr.egg-info/dependency_links.txt
+pbr.egg-info/entry_points.txt
+pbr.egg-info/not-zip-safe
+pbr.egg-info/top_level.txt
+pbr/cmd/__init__.py
+pbr/cmd/main.py
+pbr/hooks/__init__.py
+pbr/hooks/backwards.py
+pbr/hooks/base.py
+pbr/hooks/commands.py
+pbr/hooks/files.py
+pbr/hooks/metadata.py
+pbr/tests/__init__.py
+pbr/tests/base.py
+pbr/tests/test_commands.py
+pbr/tests/test_core.py
+pbr/tests/test_files.py
+pbr/tests/test_hooks.py
+pbr/tests/test_integration.py
+pbr/tests/test_packaging.py
+pbr/tests/test_pbr_json.py
+pbr/tests/test_setup.py
+pbr/tests/test_util.py
+pbr/tests/test_version.py
+pbr/tests/test_wsgi.py
+pbr/tests/util.py
+pbr/tests/testpackage/CHANGES.txt
+pbr/tests/testpackage/LICENSE.txt
+pbr/tests/testpackage/MANIFEST.in
+pbr/tests/testpackage/README.txt
+pbr/tests/testpackage/extra-file.txt
+pbr/tests/testpackage/git-extra-file.txt
+pbr/tests/testpackage/setup.cfg
+pbr/tests/testpackage/setup.py
+pbr/tests/testpackage/test-requirements.txt
+pbr/tests/testpackage/data_files/a.txt
+pbr/tests/testpackage/data_files/b.txt
+pbr/tests/testpackage/data_files/c.rst
+pbr/tests/testpackage/doc/source/conf.py
+pbr/tests/testpackage/doc/source/index.rst
+pbr/tests/testpackage/doc/source/installation.rst
+pbr/tests/testpackage/doc/source/usage.rst
+pbr/tests/testpackage/pbr_testpackage/__init__.py
+pbr/tests/testpackage/pbr_testpackage/_setup_hooks.py
+pbr/tests/testpackage/pbr_testpackage/cmd.py
+pbr/tests/testpackage/pbr_testpackage/extra.py
+pbr/tests/testpackage/pbr_testpackage/wsgi.py
+pbr/tests/testpackage/pbr_testpackage/package_data/1.txt
+pbr/tests/testpackage/pbr_testpackage/package_data/2.txt
+pbr/tests/testpackage/src/testext.c
+tools/integration.sh
+tools/tox_install.sh
\ No newline at end of file
--- /dev/null
+[console_scripts]
+pbr = pbr.cmd.main:main
+
+[distutils.commands]
+testr = pbr.testr_command:Testr
+
+[distutils.setup_keywords]
+pbr = pbr.core:pbr
+
+[egg_info.writers]
+pbr.json = pbr.pbr_json:write_pbr_json
+
--- /dev/null
+# Copyright 2011 OpenStack Foundation
+# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from distutils import log
+import fnmatch
+import os
+import pkg_resources
+import sys
+import warnings
+
+try:
+ import cStringIO
+except ImportError:
+ import io as cStringIO
+
+try:
+ from sphinx import apidoc
+ from sphinx import application
+ from sphinx import config
+ from sphinx import setup_command
+except Exception as e:
+ # NOTE(dhellmann): During the installation of docutils, setuptools
+ # tries to import pbr code to find the egg_info.writer hooks. That
+ # imports this module, which imports sphinx, which imports
+ # docutils, which is being installed. Because docutils uses 2to3
+ # to convert its code during installation under python 3, the
+ # import fails, but it fails with an error other than ImportError
+ # (today it's a NameError on StandardError, an exception base
+ # class). Convert the exception type here so it can be caught in
+ # packaging.py where we try to determine if we can import and use
+ # sphinx by importing this module. See bug #1403510 for details.
+ raise ImportError(str(e))
+from pbr import git
+from pbr import options
+
+
+_rst_template = """%(heading)s
+%(underline)s
+
+.. automodule:: %(module)s
+ :members:
+ :undoc-members:
+ :show-inheritance:
+"""
+
+
+def _find_modules(arg, dirname, files):
+ for filename in files:
+ if filename.endswith('.py') and filename != '__init__.py':
+ arg["%s.%s" % (dirname.replace('/', '.'),
+ filename[:-3])] = True
+
+
+class LocalBuildDoc(setup_command.BuildDoc):
+
+ builders = ['html', 'man']
+ command_name = 'build_sphinx'
+ sphinx_initialized = False
+
+ def _get_source_dir(self):
+ option_dict = self.distribution.get_option_dict('build_sphinx')
+ if 'source_dir' in option_dict:
+ source_dir = os.path.join(option_dict['source_dir'][1], 'api')
+ else:
+ source_dir = 'doc/source/api'
+ if not os.path.exists(source_dir):
+ os.makedirs(source_dir)
+ return source_dir
+
+ def generate_autoindex(self, excluded_modules=None):
+ log.info("[pbr] Autodocumenting from %s"
+ % os.path.abspath(os.curdir))
+ modules = {}
+ source_dir = self._get_source_dir()
+ for pkg in self.distribution.packages:
+ if '.' not in pkg:
+ for dirpath, dirnames, files in os.walk(pkg):
+ _find_modules(modules, dirpath, files)
+
+ def include(module):
+ return not any(fnmatch.fnmatch(module, pat)
+ for pat in excluded_modules)
+
+ module_list = sorted(mod for mod in modules.keys() if include(mod))
+ autoindex_filename = os.path.join(source_dir, 'autoindex.rst')
+ with open(autoindex_filename, 'w') as autoindex:
+ autoindex.write(""".. toctree::
+ :maxdepth: 1
+
+""")
+ for module in module_list:
+ output_filename = os.path.join(source_dir,
+ "%s.rst" % module)
+ heading = "The :mod:`%s` Module" % module
+ underline = "=" * len(heading)
+ values = dict(module=module, heading=heading,
+ underline=underline)
+
+ log.info("[pbr] Generating %s"
+ % output_filename)
+ with open(output_filename, 'w') as output_file:
+ output_file.write(_rst_template % values)
+ autoindex.write(" %s.rst\n" % module)
+
+ def _sphinx_tree(self):
+ source_dir = self._get_source_dir()
+ cmd = ['apidoc', '.', '-H', 'Modules', '-o', source_dir]
+ apidoc.main(cmd + self.autodoc_tree_excludes)
+
+ def _sphinx_run(self):
+ if not self.verbose:
+ status_stream = cStringIO.StringIO()
+ else:
+ status_stream = sys.stdout
+ confoverrides = {}
+ if self.project:
+ confoverrides['project'] = self.project
+ if self.version:
+ confoverrides['version'] = self.version
+ if self.release:
+ confoverrides['release'] = self.release
+ if self.today:
+ confoverrides['today'] = self.today
+ sphinx_config = config.Config(self.config_dir, 'conf.py', {}, [])
+ sphinx_ver = pkg_resources.parse_version(
+ pkg_resources.get_distribution("sphinx").version)
+ if sphinx_ver > pkg_resources.parse_version('1.2.3'):
+ sphinx_config.init_values(warnings.warn)
+ else:
+ sphinx_config.init_values()
+ if self.builder == 'man' and len(
+ getattr(sphinx_config, 'man_pages', '')) == 0:
+ return
+ if self.sphinx_initialized:
+ confoverrides['suppress_warnings'] = [
+ 'app.add_directive', 'app.add_role',
+ 'app.add_generic_role', 'app.add_node']
+ app = application.Sphinx(
+ self.source_dir, self.config_dir,
+ self.builder_target_dir, self.doctree_dir,
+ self.builder, confoverrides, status_stream,
+ freshenv=self.fresh_env, warningiserror=self.warning_is_error)
+ self.sphinx_initialized = True
+
+ try:
+ app.build(force_all=self.all_files)
+ except Exception as err:
+ from docutils import utils
+ if isinstance(err, utils.SystemMessage):
+ sys.stder.write('reST markup error:\n')
+ sys.stderr.write(err.args[0].encode('ascii',
+ 'backslashreplace'))
+ sys.stderr.write('\n')
+ else:
+ raise
+
+ if self.link_index:
+ src = app.config.master_doc + app.builder.out_suffix
+ dst = app.builder.get_outfilename('index')
+ os.symlink(src, dst)
+
+ def run(self):
+ option_dict = self.distribution.get_option_dict('pbr')
+ if git._git_is_installed():
+ git.write_git_changelog(option_dict=option_dict)
+ git.generate_authors(option_dict=option_dict)
+ tree_index = options.get_boolean_option(option_dict,
+ 'autodoc_tree_index_modules',
+ 'AUTODOC_TREE_INDEX_MODULES')
+ auto_index = options.get_boolean_option(option_dict,
+ 'autodoc_index_modules',
+ 'AUTODOC_INDEX_MODULES')
+ if not os.getenv('SPHINX_DEBUG'):
+ # NOTE(afazekas): These options can be used together,
+ # but they do a very similar thing in a different way
+ if tree_index:
+ self._sphinx_tree()
+ if auto_index:
+ self.generate_autoindex(
+ set(option_dict.get(
+ "autodoc_exclude_modules",
+ [None, ""])[1].split()))
+
+ for builder in self.builders:
+ self.builder = builder
+ self.finalize_options()
+ self._sphinx_run()
+
+ def initialize_options(self):
+ # Not a new style class, super keyword does not work.
+ setup_command.BuildDoc.initialize_options(self)
+
+ # NOTE(dstanek): exclude setup.py from the autodoc tree index
+ # builds because all projects will have an issue with it
+ self.autodoc_tree_excludes = ['setup.py']
+
+ def finalize_options(self):
+ # Not a new style class, super keyword does not work.
+ setup_command.BuildDoc.finalize_options(self)
+
+ # Handle builder option from command line - override cfg
+ option_dict = self.distribution.get_option_dict('build_sphinx')
+ if 'command line' in option_dict.get('builder', [[]])[0]:
+ self.builders = option_dict['builder'][1]
+ # Allow builders to be configurable - as a comma separated list.
+ if not isinstance(self.builders, list) and self.builders:
+ self.builders = self.builders.split(',')
+
+ self.project = self.distribution.get_name()
+ self.version = self.distribution.get_version()
+ self.release = self.distribution.get_version()
+
+ # NOTE(dstanek): check for autodoc tree exclusion overrides
+ # in the setup.cfg
+ opt = 'autodoc_tree_excludes'
+ option_dict = self.distribution.get_option_dict('pbr')
+ if opt in option_dict:
+ self.autodoc_tree_excludes = option_dict[opt][1]
+ self.ensure_string_list(opt)
+
+ # handle Sphinx < 1.5.0
+ if not hasattr(self, 'warning_is_error'):
+ self.warning_is_error = False
+
+
+class LocalBuildLatex(LocalBuildDoc):
+ builders = ['latex']
+ command_name = 'build_sphinx_latex'
--- /dev/null
+# Copyright 2014 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import argparse
+import json
+import sys
+
+import pkg_resources
+
+import pbr.version
+
+
+def _get_metadata(package_name):
+ try:
+ return json.loads(
+ pkg_resources.get_distribution(
+ package_name).get_metadata('pbr.json'))
+ except pkg_resources.DistributionNotFound:
+ raise Exception('Package {0} not installed'.format(package_name))
+ except Exception:
+ return None
+
+
+def get_sha(args):
+ sha = _get_info(args.name)['sha']
+ if sha:
+ print(sha)
+
+
+def get_info(args):
+ print("{name}\t{version}\t{released}\t{sha}".format(
+ **_get_info(args.name)))
+
+
+def _get_info(name):
+ metadata = _get_metadata(name)
+ version = pkg_resources.get_distribution(name).version
+ if metadata:
+ if metadata['is_release']:
+ released = 'released'
+ else:
+ released = 'pre-release'
+ sha = metadata['git_version']
+ else:
+ version_parts = version.split('.')
+ if version_parts[-1].startswith('g'):
+ sha = version_parts[-1][1:]
+ released = 'pre-release'
+ else:
+ sha = ""
+ released = "released"
+ for part in version_parts:
+ if not part.isdigit():
+ released = "pre-release"
+ return dict(name=name, version=version, sha=sha, released=released)
+
+
+def freeze(args):
+ sorted_dists = sorted(pkg_resources.working_set,
+ key=lambda dist: dist.project_name.lower())
+ for dist in sorted_dists:
+ info = _get_info(dist.project_name)
+ output = "{name}=={version}".format(**info)
+ if info['sha']:
+ output += " # git sha {sha}".format(**info)
+ print(output)
+
+
+def main():
+ parser = argparse.ArgumentParser(
+ description='pbr: Python Build Reasonableness')
+ parser.add_argument(
+ '-v', '--version', action='version',
+ version=str(pbr.version.VersionInfo('pbr')))
+
+ subparsers = parser.add_subparsers(
+ title='commands', description='valid commands', help='additional help')
+
+ cmd_sha = subparsers.add_parser('sha', help='print sha of package')
+ cmd_sha.set_defaults(func=get_sha)
+ cmd_sha.add_argument('name', help='package to print sha of')
+
+ cmd_info = subparsers.add_parser(
+ 'info', help='print version info for package')
+ cmd_info.set_defaults(func=get_info)
+ cmd_info.add_argument('name', help='package to print info of')
+
+ cmd_freeze = subparsers.add_parser(
+ 'freeze', help='print version info for all installed packages')
+ cmd_freeze.set_defaults(func=freeze)
+
+ args = parser.parse_args()
+ try:
+ args.func(args)
+ except Exception as e:
+ print(e)
+
+
+if __name__ == '__main__':
+ sys.exit(main())
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+
+from distutils import core
+from distutils import errors
+import logging
+import os
+import sys
+import warnings
+
+from setuptools import dist
+
+from pbr import util
+
+
+_saved_core_distribution = core.Distribution
+
+
+def _monkeypatch_distribution():
+ core.Distribution = dist._get_unpatched(core.Distribution)
+
+
+def _restore_distribution_monkeypatch():
+ core.Distribution = _saved_core_distribution
+
+
+if sys.version_info[0] == 3:
+ string_type = str
+ integer_types = (int,)
+else:
+ string_type = basestring # flake8: noqa
+ integer_types = (int, long) # flake8: noqa
+
+
+def pbr(dist, attr, value):
+ """Implements the actual pbr setup() keyword.
+
+ When used, this should be the only keyword in your setup() aside from
+ `setup_requires`.
+
+ If given as a string, the value of pbr is assumed to be the relative path
+ to the setup.cfg file to use. Otherwise, if it evaluates to true, it
+ simply assumes that pbr should be used, and the default 'setup.cfg' is
+ used.
+
+ This works by reading the setup.cfg file, parsing out the supported
+ metadata and command options, and using them to rebuild the
+ `DistributionMetadata` object and set the newly added command options.
+
+ The reason for doing things this way is that a custom `Distribution` class
+ will not play nicely with setup_requires; however, this implementation may
+ not work well with distributions that do use a `Distribution` subclass.
+ """
+
+ try:
+ _monkeypatch_distribution()
+ if not value:
+ return
+ if isinstance(value, string_type):
+ path = os.path.abspath(value)
+ else:
+ path = os.path.abspath('setup.cfg')
+ if not os.path.exists(path):
+ raise errors.DistutilsFileError(
+ 'The setup.cfg file %s does not exist.' % path)
+
+ # Converts the setup.cfg file to setup() arguments
+ try:
+ attrs = util.cfg_to_args(path, dist.script_args)
+ except Exception:
+ e = sys.exc_info()[1]
+ # NB: This will output to the console if no explicit logging has
+ # been setup - but thats fine, this is a fatal distutils error, so
+ # being pretty isn't the #1 goal.. being diagnosable is.
+ logging.exception('Error parsing')
+ raise errors.DistutilsSetupError(
+ 'Error parsing %s: %s: %s' % (path, e.__class__.__name__, e))
+
+ # Repeat some of the Distribution initialization code with the newly
+ # provided attrs
+ if attrs:
+ # Skips 'options' and 'licence' support which are rarely used; may
+ # add back in later if demanded
+ for key, val in attrs.items():
+ if hasattr(dist.metadata, 'set_' + key):
+ getattr(dist.metadata, 'set_' + key)(val)
+ elif hasattr(dist.metadata, key):
+ setattr(dist.metadata, key, val)
+ elif hasattr(dist, key):
+ setattr(dist, key, val)
+ else:
+ msg = 'Unknown distribution option: %s' % repr(key)
+ warnings.warn(msg)
+
+ # Re-finalize the underlying Distribution
+ core.Distribution.finalize_options(dist)
+
+ # This bit comes out of distribute/setuptools
+ if isinstance(dist.metadata.version, integer_types + (float,)):
+ # Some people apparently take "version number" too literally :)
+ dist.metadata.version = str(dist.metadata.version)
+
+ # This bit of hackery is necessary so that the Distribution will ignore
+ # normally unsupport command options (namely pre-hooks and post-hooks).
+ # dist.command_options is normally a dict mapping command names to
+ # dicts of their options. Now it will be a defaultdict that returns
+ # IgnoreDicts for the each command's options so we can pass through the
+ # unsupported options
+ ignore = ['pre_hook.*', 'post_hook.*']
+ dist.command_options = util.DefaultGetDict(
+ lambda: util.IgnoreDict(ignore)
+ )
+ finally:
+ _restore_distribution_monkeypatch()
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from distutils import errors
+import os
+
+_extra_files = []
+
+
+def get_extra_files():
+ global _extra_files
+ return _extra_files
+
+
+def set_extra_files(extra_files):
+ # Let's do a sanity check
+ for filename in extra_files:
+ if not os.path.exists(filename):
+ raise errors.DistutilsFileError(
+ '%s from the extra_files option in setup.cfg does not '
+ 'exist' % filename)
+ global _extra_files
+ _extra_files[:] = extra_files[:]
--- /dev/null
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+
+import setuptools
+
+
+def smart_find_packages(package_list):
+ """Run find_packages the way we intend."""
+ packages = []
+ for pkg in package_list.strip().split("\n"):
+ pkg_path = pkg.replace('.', os.path.sep)
+ packages.append(pkg)
+ packages.extend(['%s.%s' % (pkg, f)
+ for f in setuptools.find_packages(pkg_path)])
+ return "\n".join(set(packages))
--- /dev/null
+# Copyright 2011 OpenStack Foundation
+# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from __future__ import unicode_literals
+
+import distutils.errors
+from distutils import log
+import errno
+import io
+import os
+import re
+import subprocess
+import time
+
+import pkg_resources
+
+from pbr import options
+from pbr import version
+
+
+def _run_shell_command(cmd, throw_on_error=False, buffer=True, env=None):
+ if buffer:
+ out_location = subprocess.PIPE
+ err_location = subprocess.PIPE
+ else:
+ out_location = None
+ err_location = None
+
+ newenv = os.environ.copy()
+ if env:
+ newenv.update(env)
+
+ output = subprocess.Popen(cmd,
+ stdout=out_location,
+ stderr=err_location,
+ env=newenv)
+ out = output.communicate()
+ if output.returncode and throw_on_error:
+ raise distutils.errors.DistutilsError(
+ "%s returned %d" % (cmd, output.returncode))
+ if len(out) == 0 or not out[0] or not out[0].strip():
+ return ''
+ # Since we don't control the history, and forcing users to rebase arbitrary
+ # history to fix utf8 issues is harsh, decode with replace.
+ return out[0].strip().decode('utf-8', 'replace')
+
+
+def _run_git_command(cmd, git_dir, **kwargs):
+ if not isinstance(cmd, (list, tuple)):
+ cmd = [cmd]
+ return _run_shell_command(
+ ['git', '--git-dir=%s' % git_dir] + cmd, **kwargs)
+
+
+def _get_git_directory():
+ try:
+ return _run_shell_command(['git', 'rev-parse', '--git-dir'])
+ except OSError as e:
+ if e.errno == errno.ENOENT:
+ # git not installed.
+ return ''
+ raise
+
+
+def _git_is_installed():
+ try:
+ # We cannot use 'which git' as it may not be available
+ # in some distributions, So just try 'git --version'
+ # to see if we run into trouble
+ _run_shell_command(['git', '--version'])
+ except OSError:
+ return False
+ return True
+
+
+def _get_highest_tag(tags):
+ """Find the highest tag from a list.
+
+ Pass in a list of tag strings and this will return the highest
+ (latest) as sorted by the pkg_resources version parser.
+ """
+ return max(tags, key=pkg_resources.parse_version)
+
+
+def _find_git_files(dirname='', git_dir=None):
+ """Behave like a file finder entrypoint plugin.
+
+ We don't actually use the entrypoints system for this because it runs
+ at absurd times. We only want to do this when we are building an sdist.
+ """
+ file_list = []
+ if git_dir is None:
+ git_dir = _run_git_functions()
+ if git_dir:
+ log.info("[pbr] In git context, generating filelist from git")
+ file_list = _run_git_command(['ls-files', '-z'], git_dir)
+ # Users can fix utf8 issues locally with a single commit, so we are
+ # strict here.
+ file_list = file_list.split(b'\x00'.decode('utf-8'))
+ return [f for f in file_list if f]
+
+
+def _get_raw_tag_info(git_dir):
+ describe = _run_git_command(['describe', '--always'], git_dir)
+ if "-" in describe:
+ return describe.rsplit("-", 2)[-2]
+ if "." in describe:
+ return 0
+ return None
+
+
+def get_is_release(git_dir):
+ return _get_raw_tag_info(git_dir) == 0
+
+
+def _run_git_functions():
+ git_dir = None
+ if _git_is_installed():
+ git_dir = _get_git_directory()
+ return git_dir or None
+
+
+def get_git_short_sha(git_dir=None):
+ """Return the short sha for this repo, if it exists."""
+ if not git_dir:
+ git_dir = _run_git_functions()
+ if git_dir:
+ return _run_git_command(
+ ['log', '-n1', '--pretty=format:%h'], git_dir)
+ return None
+
+
+def _iter_changelog(changelog):
+ """Convert a oneline log iterator to formatted strings.
+
+ :param changelog: An iterator of one line log entries like
+ that given by _iter_log_oneline.
+ :return: An iterator over (release, formatted changelog) tuples.
+ """
+ first_line = True
+ current_release = None
+ yield current_release, "CHANGES\n=======\n\n"
+ for hash, tags, msg in changelog:
+ if tags:
+ current_release = _get_highest_tag(tags)
+ underline = len(current_release) * '-'
+ if not first_line:
+ yield current_release, '\n'
+ yield current_release, (
+ "%(tag)s\n%(underline)s\n\n" %
+ dict(tag=current_release, underline=underline))
+
+ if not msg.startswith("Merge "):
+ if msg.endswith("."):
+ msg = msg[:-1]
+ yield current_release, "* %(msg)s\n" % dict(msg=msg)
+ first_line = False
+
+
+def _iter_log_oneline(git_dir=None):
+ """Iterate over --oneline log entries if possible.
+
+ This parses the output into a structured form but does not apply
+ presentation logic to the output - making it suitable for different
+ uses.
+
+ :return: An iterator of (hash, tags_set, 1st_line) tuples, or None if
+ changelog generation is disabled / not available.
+ """
+ if git_dir is None:
+ git_dir = _get_git_directory()
+ if not git_dir:
+ return []
+ return _iter_log_inner(git_dir)
+
+
+def _is_valid_version(candidate):
+ try:
+ version.SemanticVersion.from_pip_string(candidate)
+ return True
+ except ValueError:
+ return False
+
+
+def _iter_log_inner(git_dir):
+ """Iterate over --oneline log entries.
+
+ This parses the output intro a structured form but does not apply
+ presentation logic to the output - making it suitable for different
+ uses.
+
+ :return: An iterator of (hash, tags_set, 1st_line) tuples.
+ """
+ log.info('[pbr] Generating ChangeLog')
+ log_cmd = ['log', '--decorate=full', '--format=%h%x00%s%x00%d']
+ changelog = _run_git_command(log_cmd, git_dir)
+ for line in changelog.split('\n'):
+ line_parts = line.split('\x00')
+ if len(line_parts) != 3:
+ continue
+ sha, msg, refname = line_parts
+ tags = set()
+
+ # refname can be:
+ # <empty>
+ # HEAD, tag: refs/tags/1.4.0, refs/remotes/origin/master, \
+ # refs/heads/master
+ # refs/tags/1.3.4
+ if "refs/tags/" in refname:
+ refname = refname.strip()[1:-1] # remove wrapping ()'s
+ # If we start with "tag: refs/tags/1.2b1, tag: refs/tags/1.2"
+ # The first split gives us "['', '1.2b1, tag:', '1.2']"
+ # Which is why we do the second split below on the comma
+ for tag_string in refname.split("refs/tags/")[1:]:
+ # git tag does not allow : or " " in tag names, so we split
+ # on ", " which is the separator between elements
+ candidate = tag_string.split(", ")[0]
+ if _is_valid_version(candidate):
+ tags.add(candidate)
+
+ yield sha, tags, msg
+
+
+def write_git_changelog(git_dir=None, dest_dir=os.path.curdir,
+ option_dict=None, changelog=None):
+ """Write a changelog based on the git changelog."""
+ start = time.time()
+ if not option_dict:
+ option_dict = {}
+ should_skip = options.get_boolean_option(option_dict, 'skip_changelog',
+ 'SKIP_WRITE_GIT_CHANGELOG')
+ if should_skip:
+ return
+ if not changelog:
+ changelog = _iter_log_oneline(git_dir=git_dir)
+ if changelog:
+ changelog = _iter_changelog(changelog)
+ if not changelog:
+ return
+ new_changelog = os.path.join(dest_dir, 'ChangeLog')
+ # If there's already a ChangeLog and it's not writable, just use it
+ if (os.path.exists(new_changelog)
+ and not os.access(new_changelog, os.W_OK)):
+ log.info('[pbr] ChangeLog not written (file already'
+ ' exists and it is not writeable)')
+ return
+ log.info('[pbr] Writing ChangeLog')
+ with io.open(new_changelog, "w", encoding="utf-8") as changelog_file:
+ for release, content in changelog:
+ changelog_file.write(content)
+ stop = time.time()
+ log.info('[pbr] ChangeLog complete (%0.1fs)' % (stop - start))
+
+
+def generate_authors(git_dir=None, dest_dir='.', option_dict=dict()):
+ """Create AUTHORS file using git commits."""
+ should_skip = options.get_boolean_option(option_dict, 'skip_authors',
+ 'SKIP_GENERATE_AUTHORS')
+ if should_skip:
+ return
+ start = time.time()
+ old_authors = os.path.join(dest_dir, 'AUTHORS.in')
+ new_authors = os.path.join(dest_dir, 'AUTHORS')
+ # If there's already an AUTHORS file and it's not writable, just use it
+ if (os.path.exists(new_authors)
+ and not os.access(new_authors, os.W_OK)):
+ return
+ log.info('[pbr] Generating AUTHORS')
+ ignore_emails = '(jenkins@review|infra@lists|jenkins@openstack)'
+ if git_dir is None:
+ git_dir = _get_git_directory()
+ if git_dir:
+ authors = []
+
+ # don't include jenkins email address in AUTHORS file
+ git_log_cmd = ['log', '--format=%aN <%aE>']
+ authors += _run_git_command(git_log_cmd, git_dir).split('\n')
+ authors = [a for a in authors if not re.search(ignore_emails, a)]
+
+ # get all co-authors from commit messages
+ co_authors_out = _run_git_command('log', git_dir)
+ co_authors = re.findall('Co-authored-by:.+', co_authors_out,
+ re.MULTILINE)
+ co_authors = [signed.split(":", 1)[1].strip()
+ for signed in co_authors if signed]
+
+ authors += co_authors
+ authors = sorted(set(authors))
+
+ with open(new_authors, 'wb') as new_authors_fh:
+ if os.path.exists(old_authors):
+ with open(old_authors, "rb") as old_authors_fh:
+ new_authors_fh.write(old_authors_fh.read())
+ new_authors_fh.write(('\n'.join(authors) + '\n')
+ .encode('utf-8'))
+ stop = time.time()
+ log.info('[pbr] AUTHORS complete (%0.1fs)' % (stop - start))
--- /dev/null
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from pbr.hooks import backwards
+from pbr.hooks import commands
+from pbr.hooks import files
+from pbr.hooks import metadata
+
+
+def setup_hook(config):
+ """Filter config parsed from a setup.cfg to inject our defaults."""
+ metadata_config = metadata.MetadataConfig(config)
+ metadata_config.run()
+ backwards.BackwardsCompatConfig(config).run()
+ commands.CommandsConfig(config).run()
+ files.FilesConfig(config, metadata_config.get_name()).run()
--- /dev/null
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from pbr.hooks import base
+from pbr import packaging
+
+
+class BackwardsCompatConfig(base.BaseConfig):
+
+ section = 'backwards_compat'
+
+ def hook(self):
+ self.config['include_package_data'] = 'True'
+ packaging.append_text_list(
+ self.config, 'dependency_links',
+ packaging.parse_dependency_links())
+ packaging.append_text_list(
+ self.config, 'tests_require',
+ packaging.parse_requirements(
+ packaging.TEST_REQUIREMENTS_FILES,
+ strip_markers=True))
--- /dev/null
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+
+class BaseConfig(object):
+
+ section = None
+
+ def __init__(self, config):
+ self._global_config = config
+ self.config = self._global_config.get(self.section, dict())
+ self.pbr_config = config.get('pbr', dict())
+
+ def run(self):
+ self.hook()
+ self.save()
+
+ def hook(self):
+ pass
+
+ def save(self):
+ self._global_config[self.section] = self.config
--- /dev/null
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+
+from setuptools.command import easy_install
+
+from pbr.hooks import base
+from pbr import options
+from pbr import packaging
+
+
+class CommandsConfig(base.BaseConfig):
+
+ section = 'global'
+
+ def __init__(self, config):
+ super(CommandsConfig, self).__init__(config)
+ self.commands = self.config.get('commands', "")
+
+ def save(self):
+ self.config['commands'] = self.commands
+ super(CommandsConfig, self).save()
+
+ def add_command(self, command):
+ self.commands = "%s\n%s" % (self.commands, command)
+
+ def hook(self):
+ self.add_command('pbr.packaging.LocalEggInfo')
+ self.add_command('pbr.packaging.LocalSDist')
+ self.add_command('pbr.packaging.LocalInstallScripts')
+ self.add_command('pbr.packaging.LocalDevelop')
+ self.add_command('pbr.packaging.LocalRPMVersion')
+ self.add_command('pbr.packaging.LocalDebVersion')
+ if os.name != 'nt':
+ easy_install.get_script_args = packaging.override_get_script_args
+
+ if packaging.have_sphinx():
+ self.add_command('pbr.builddoc.LocalBuildDoc')
+ self.add_command('pbr.builddoc.LocalBuildLatex')
+
+ if os.path.exists('.testr.conf') and packaging.have_testr():
+ # There is a .testr.conf file. We want to use it.
+ self.add_command('pbr.packaging.TestrTest')
+ elif self.config.get('nosetests', False) and packaging.have_nose():
+ # We seem to still have nose configured
+ self.add_command('pbr.packaging.NoseTest')
+
+ use_egg = options.get_boolean_option(
+ self.pbr_config, 'use-egg', 'PBR_USE_EGG')
+ # We always want non-egg install unless explicitly requested
+ if 'manpages' in self.pbr_config or not use_egg:
+ self.add_command('pbr.packaging.LocalInstall')
+ else:
+ self.add_command('pbr.packaging.InstallWithGit')
--- /dev/null
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+import sys
+
+from pbr import find_package
+from pbr.hooks import base
+
+
+def get_manpath():
+ manpath = 'share/man'
+ if os.path.exists(os.path.join(sys.prefix, 'man')):
+ # This works around a bug with install where it expects every node
+ # in the relative data directory to be an actual directory, since at
+ # least Debian derivatives (and probably other platforms as well)
+ # like to symlink Unixish /usr/local/man to /usr/local/share/man.
+ manpath = 'man'
+ return manpath
+
+
+def get_man_section(section):
+ return os.path.join(get_manpath(), 'man%s' % section)
+
+
+class FilesConfig(base.BaseConfig):
+
+ section = 'files'
+
+ def __init__(self, config, name):
+ super(FilesConfig, self).__init__(config)
+ self.name = name
+ self.data_files = self.config.get('data_files', '')
+
+ def save(self):
+ self.config['data_files'] = self.data_files
+ super(FilesConfig, self).save()
+
+ def expand_globs(self):
+ finished = []
+ for line in self.data_files.split("\n"):
+ if line.rstrip().endswith('*') and '=' in line:
+ (target, source_glob) = line.split('=')
+ source_prefix = source_glob.strip()[:-1]
+ target = target.strip()
+ if not target.endswith(os.path.sep):
+ target += os.path.sep
+ for (dirpath, dirnames, fnames) in os.walk(source_prefix):
+ finished.append(
+ "%s = " % dirpath.replace(source_prefix, target))
+ finished.extend(
+ [" %s" % os.path.join(dirpath, f) for f in fnames])
+ else:
+ finished.append(line)
+
+ self.data_files = "\n".join(finished)
+
+ def add_man_path(self, man_path):
+ self.data_files = "%s\n%s =" % (self.data_files, man_path)
+
+ def add_man_page(self, man_page):
+ self.data_files = "%s\n %s" % (self.data_files, man_page)
+
+ def get_man_sections(self):
+ man_sections = dict()
+ manpages = self.pbr_config['manpages']
+ for manpage in manpages.split():
+ section_number = manpage.strip()[-1]
+ section = man_sections.get(section_number, list())
+ section.append(manpage.strip())
+ man_sections[section_number] = section
+ return man_sections
+
+ def hook(self):
+ packages = self.config.get('packages', self.name).strip()
+ expanded = []
+ for pkg in packages.split("\n"):
+ if os.path.isdir(pkg.strip()):
+ expanded.append(find_package.smart_find_packages(pkg.strip()))
+
+ self.config['packages'] = "\n".join(expanded)
+
+ self.expand_globs()
+
+ if 'manpages' in self.pbr_config:
+ man_sections = self.get_man_sections()
+ for (section, pages) in man_sections.items():
+ manpath = get_man_section(section)
+ self.add_man_path(manpath)
+ for page in pages:
+ self.add_man_page(page)
--- /dev/null
+# Copyright 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from pbr.hooks import base
+from pbr import packaging
+
+
+class MetadataConfig(base.BaseConfig):
+
+ section = 'metadata'
+
+ def hook(self):
+ self.config['version'] = packaging.get_version(
+ self.config['name'], self.config.get('version', None))
+ packaging.append_text_list(
+ self.config, 'requires_dist',
+ packaging.parse_requirements())
+
+ def get_name(self):
+ return self.config['name']
--- /dev/null
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+
+import os
+
+
+TRUE_VALUES = ('true', '1', 'yes')
+
+
+def get_boolean_option(option_dict, option_name, env_name):
+ return ((option_name in option_dict
+ and option_dict[option_name][1].lower() in TRUE_VALUES) or
+ str(os.getenv(env_name)).lower() in TRUE_VALUES)
--- /dev/null
+# Copyright 2011 OpenStack Foundation
+# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Utilities with minimum-depends for use in setup.py
+"""
+
+from __future__ import unicode_literals
+
+from distutils.command import install as du_install
+from distutils import log
+import email
+import email.errors
+import os
+import re
+import sys
+
+import pkg_resources
+import setuptools
+from setuptools.command import develop
+from setuptools.command import easy_install
+from setuptools.command import egg_info
+from setuptools.command import install
+from setuptools.command import install_scripts
+from setuptools.command import sdist
+
+from pbr import extra_files
+from pbr import git
+from pbr import options
+import pbr.pbr_json
+from pbr import testr_command
+from pbr import version
+
+REQUIREMENTS_FILES = ('requirements.txt', 'tools/pip-requires')
+TEST_REQUIREMENTS_FILES = ('test-requirements.txt', 'tools/test-requires')
+
+
+def get_requirements_files():
+ files = os.environ.get("PBR_REQUIREMENTS_FILES")
+ if files:
+ return tuple(f.strip() for f in files.split(','))
+ # Returns a list composed of:
+ # - REQUIREMENTS_FILES with -py2 or -py3 in the name
+ # (e.g. requirements-py3.txt)
+ # - REQUIREMENTS_FILES
+ return (list(map(('-py' + str(sys.version_info[0])).join,
+ map(os.path.splitext, REQUIREMENTS_FILES)))
+ + list(REQUIREMENTS_FILES))
+
+
+def append_text_list(config, key, text_list):
+ """Append a \n separated list to possibly existing value."""
+ new_value = []
+ current_value = config.get(key, "")
+ if current_value:
+ new_value.append(current_value)
+ new_value.extend(text_list)
+ config[key] = '\n'.join(new_value)
+
+
+def _any_existing(file_list):
+ return [f for f in file_list if os.path.exists(f)]
+
+
+# Get requirements from the first file that exists
+def get_reqs_from_files(requirements_files):
+ for requirements_file in _any_existing(requirements_files):
+ with open(requirements_file, 'r') as fil:
+ return fil.read().split('\n')
+ return []
+
+
+def parse_requirements(requirements_files=None, strip_markers=False):
+
+ if requirements_files is None:
+ requirements_files = get_requirements_files()
+
+ def egg_fragment(match):
+ # take a versioned egg fragment and return a
+ # versioned package requirement e.g.
+ # nova-1.2.3 becomes nova>=1.2.3
+ return re.sub(r'([\w.]+)-([\w.-]+)',
+ r'\1>=\2',
+ match.groups()[-1])
+
+ requirements = []
+ for line in get_reqs_from_files(requirements_files):
+ # Ignore comments
+ if (not line.strip()) or line.startswith('#'):
+ continue
+
+ # Handle nested requirements files such as:
+ # -r other-requirements.txt
+ if line.startswith('-r'):
+ req_file = line.partition(' ')[2]
+ requirements += parse_requirements(
+ [req_file], strip_markers=strip_markers)
+ continue
+
+ try:
+ project_name = pkg_resources.Requirement.parse(line).project_name
+ except ValueError:
+ project_name = None
+
+ # For the requirements list, we need to inject only the portion
+ # after egg= so that distutils knows the package it's looking for
+ # such as:
+ # -e git://github.com/openstack/nova/master#egg=nova
+ # -e git://github.com/openstack/nova/master#egg=nova-1.2.3
+ if re.match(r'\s*-e\s+', line):
+ line = re.sub(r'\s*-e\s+.*#egg=(.*)$', egg_fragment, line)
+ # such as:
+ # http://github.com/openstack/nova/zipball/master#egg=nova
+ # http://github.com/openstack/nova/zipball/master#egg=nova-1.2.3
+ elif re.match(r'\s*(https?|git(\+(https|ssh))?):', line):
+ line = re.sub(r'\s*(https?|git(\+(https|ssh))?):.*#egg=(.*)$',
+ egg_fragment, line)
+ # -f lines are for index locations, and don't get used here
+ elif re.match(r'\s*-f\s+', line):
+ line = None
+ reason = 'Index Location'
+
+ if line is not None:
+ line = re.sub('#.*$', '', line)
+ if strip_markers:
+ semi_pos = line.find(';')
+ if semi_pos < 0:
+ semi_pos = None
+ line = line[:semi_pos]
+ requirements.append(line)
+ else:
+ log.info(
+ '[pbr] Excluding %s: %s' % (project_name, reason))
+
+ return requirements
+
+
+def parse_dependency_links(requirements_files=None):
+ if requirements_files is None:
+ requirements_files = get_requirements_files()
+ dependency_links = []
+ # dependency_links inject alternate locations to find packages listed
+ # in requirements
+ for line in get_reqs_from_files(requirements_files):
+ # skip comments and blank lines
+ if re.match(r'(\s*#)|(\s*$)', line):
+ continue
+ # lines with -e or -f need the whole line, minus the flag
+ if re.match(r'\s*-[ef]\s+', line):
+ dependency_links.append(re.sub(r'\s*-[ef]\s+', '', line))
+ # lines that are only urls can go in unmolested
+ elif re.match(r'\s*(https?|git(\+(https|ssh))?):', line):
+ dependency_links.append(line)
+ return dependency_links
+
+
+class InstallWithGit(install.install):
+ """Extracts ChangeLog and AUTHORS from git then installs.
+
+ This is useful for e.g. readthedocs where the package is
+ installed and then docs built.
+ """
+
+ command_name = 'install'
+
+ def run(self):
+ _from_git(self.distribution)
+ return install.install.run(self)
+
+
+class LocalInstall(install.install):
+ """Runs python setup.py install in a sensible manner.
+
+ Force a non-egg installed in the manner of
+ single-version-externally-managed, which allows us to install manpages
+ and config files.
+ """
+
+ command_name = 'install'
+
+ def run(self):
+ _from_git(self.distribution)
+ return du_install.install.run(self)
+
+
+class TestrTest(testr_command.Testr):
+ """Make setup.py test do the right thing."""
+
+ command_name = 'test'
+
+ def run(self):
+ # Can't use super - base class old-style class
+ testr_command.Testr.run(self)
+
+
+class LocalRPMVersion(setuptools.Command):
+ __doc__ = """Output the rpm *compatible* version string of this package"""
+ description = __doc__
+
+ user_options = []
+ command_name = "rpm_version"
+
+ def run(self):
+ log.info("[pbr] Extracting rpm version")
+ name = self.distribution.get_name()
+ print(version.VersionInfo(name).semantic_version().rpm_string())
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+
+class LocalDebVersion(setuptools.Command):
+ __doc__ = """Output the deb *compatible* version string of this package"""
+ description = __doc__
+
+ user_options = []
+ command_name = "deb_version"
+
+ def run(self):
+ log.info("[pbr] Extracting deb version")
+ name = self.distribution.get_name()
+ print(version.VersionInfo(name).semantic_version().debian_string())
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+
+def have_testr():
+ return testr_command.have_testr
+
+
+try:
+ from nose import commands
+
+ class NoseTest(commands.nosetests):
+ """Fallback test runner if testr is a no-go."""
+
+ command_name = 'test'
+
+ def run(self):
+ # Can't use super - base class old-style class
+ commands.nosetests.run(self)
+
+ _have_nose = True
+
+except ImportError:
+ _have_nose = False
+
+
+def have_nose():
+ return _have_nose
+
+_wsgi_text = """#PBR Generated from %(group)r
+
+import threading
+
+from %(module_name)s import %(import_target)s
+
+if __name__ == "__main__":
+ import argparse
+ import socket
+ import sys
+ import wsgiref.simple_server as wss
+
+ my_ip = socket.gethostbyname(socket.gethostname())
+ parser = argparse.ArgumentParser(
+ description=%(import_target)s.__doc__,
+ formatter_class=argparse.ArgumentDefaultsHelpFormatter,
+ usage='%%(prog)s [-h] [--port PORT] -- [passed options]')
+ parser.add_argument('--port', '-p', type=int, default=8000,
+ help='TCP port to listen on')
+ parser.add_argument('args',
+ nargs=argparse.REMAINDER,
+ metavar='-- [passed options]',
+ help="'--' is the separator of the arguments used "
+ "to start the WSGI server and the arguments passed "
+ "to the WSGI application.")
+ args = parser.parse_args()
+ if args.args:
+ if args.args[0] == '--':
+ args.args.pop(0)
+ else:
+ parser.error("unrecognized arguments: %%s" %% ' '.join(args.args))
+ sys.argv[1:] = args.args
+ server = wss.make_server('', args.port, %(invoke_target)s())
+
+ print("*" * 80)
+ print("STARTING test server %(module_name)s.%(invoke_target)s")
+ url = "http://%%s:%%d/" %% (my_ip, server.server_port)
+ print("Available at %%s" %% url)
+ print("DANGER! For testing only, do not use in production")
+ print("*" * 80)
+ sys.stdout.flush()
+
+ server.serve_forever()
+else:
+ application = None
+ app_lock = threading.Lock()
+
+ with app_lock:
+ if application is None:
+ application = %(invoke_target)s()
+
+"""
+
+_script_text = """# PBR Generated from %(group)r
+
+import sys
+
+from %(module_name)s import %(import_target)s
+
+
+if __name__ == "__main__":
+ sys.exit(%(invoke_target)s())
+"""
+
+
+# the following allows us to specify different templates per entry
+# point group when generating pbr scripts.
+ENTRY_POINTS_MAP = {
+ 'console_scripts': _script_text,
+ 'gui_scripts': _script_text,
+ 'wsgi_scripts': _wsgi_text
+}
+
+
+def generate_script(group, entry_point, header, template):
+ """Generate the script based on the template.
+
+ :param str group:
+ The entry-point group name, e.g., "console_scripts".
+ :param str header:
+ The first line of the script, e.g., "!#/usr/bin/env python".
+ :param str template:
+ The script template.
+ :returns:
+ The templated script content
+ :rtype:
+ str
+ """
+ if not entry_point.attrs or len(entry_point.attrs) > 2:
+ raise ValueError("Script targets must be of the form "
+ "'func' or 'Class.class_method'.")
+ script_text = template % dict(
+ group=group,
+ module_name=entry_point.module_name,
+ import_target=entry_point.attrs[0],
+ invoke_target='.'.join(entry_point.attrs),
+ )
+ return header + script_text
+
+
+def override_get_script_args(
+ dist, executable=os.path.normpath(sys.executable), is_wininst=False):
+ """Override entrypoints console_script."""
+ header = easy_install.get_script_header("", executable, is_wininst)
+ for group, template in ENTRY_POINTS_MAP.items():
+ for name, ep in dist.get_entry_map(group).items():
+ yield (name, generate_script(group, ep, header, template))
+
+
+class LocalDevelop(develop.develop):
+
+ command_name = 'develop'
+
+ def install_wrapper_scripts(self, dist):
+ if sys.platform == 'win32':
+ return develop.develop.install_wrapper_scripts(self, dist)
+ if not self.exclude_scripts:
+ for args in override_get_script_args(dist):
+ self.write_script(*args)
+
+
+class LocalInstallScripts(install_scripts.install_scripts):
+ """Intercepts console scripts entry_points."""
+ command_name = 'install_scripts'
+
+ def _make_wsgi_scripts_only(self, dist, executable, is_wininst):
+ header = easy_install.get_script_header("", executable, is_wininst)
+ wsgi_script_template = ENTRY_POINTS_MAP['wsgi_scripts']
+ for name, ep in dist.get_entry_map('wsgi_scripts').items():
+ content = generate_script(
+ 'wsgi_scripts', ep, header, wsgi_script_template)
+ self.write_script(name, content)
+
+ def run(self):
+ import distutils.command.install_scripts
+
+ self.run_command("egg_info")
+ if self.distribution.scripts:
+ # run first to set up self.outfiles
+ distutils.command.install_scripts.install_scripts.run(self)
+ else:
+ self.outfiles = []
+
+ ei_cmd = self.get_finalized_command("egg_info")
+ dist = pkg_resources.Distribution(
+ ei_cmd.egg_base,
+ pkg_resources.PathMetadata(ei_cmd.egg_base, ei_cmd.egg_info),
+ ei_cmd.egg_name, ei_cmd.egg_version,
+ )
+ bs_cmd = self.get_finalized_command('build_scripts')
+ executable = getattr(
+ bs_cmd, 'executable', easy_install.sys_executable)
+ is_wininst = getattr(
+ self.get_finalized_command("bdist_wininst"), '_is_running', False
+ )
+
+ if 'bdist_wheel' in self.distribution.have_run:
+ # We're building a wheel which has no way of generating mod_wsgi
+ # scripts for us. Let's build them.
+ # NOTE(sigmavirus24): This needs to happen here because, as the
+ # comment below indicates, no_ep is True when building a wheel.
+ self._make_wsgi_scripts_only(dist, executable, is_wininst)
+
+ if self.no_ep:
+ # no_ep is True if we're installing into an .egg file or building
+ # a .whl file, in those cases, we do not want to build all of the
+ # entry-points listed for this package.
+ return
+
+ if os.name != 'nt':
+ get_script_args = override_get_script_args
+ else:
+ get_script_args = easy_install.get_script_args
+ executable = '"%s"' % executable
+
+ for args in get_script_args(dist, executable, is_wininst):
+ self.write_script(*args)
+
+
+class LocalManifestMaker(egg_info.manifest_maker):
+ """Add any files that are in git and some standard sensible files."""
+
+ def _add_pbr_defaults(self):
+ for template_line in [
+ 'include AUTHORS',
+ 'include ChangeLog',
+ 'exclude .gitignore',
+ 'exclude .gitreview',
+ 'global-exclude *.pyc'
+ ]:
+ self.filelist.process_template_line(template_line)
+
+ def add_defaults(self):
+ option_dict = self.distribution.get_option_dict('pbr')
+
+ sdist.sdist.add_defaults(self)
+ self.filelist.append(self.template)
+ self.filelist.append(self.manifest)
+ self.filelist.extend(extra_files.get_extra_files())
+ should_skip = options.get_boolean_option(option_dict, 'skip_git_sdist',
+ 'SKIP_GIT_SDIST')
+ if not should_skip:
+ rcfiles = git._find_git_files()
+ if rcfiles:
+ self.filelist.extend(rcfiles)
+ elif os.path.exists(self.manifest):
+ self.read_manifest()
+ ei_cmd = self.get_finalized_command('egg_info')
+ self._add_pbr_defaults()
+ self.filelist.include_pattern("*", prefix=ei_cmd.egg_info)
+
+
+class LocalEggInfo(egg_info.egg_info):
+ """Override the egg_info command to regenerate SOURCES.txt sensibly."""
+
+ command_name = 'egg_info'
+
+ def find_sources(self):
+ """Generate SOURCES.txt only if there isn't one already.
+
+ If we are in an sdist command, then we always want to update
+ SOURCES.txt. If we are not in an sdist command, then it doesn't
+ matter one flip, and is actually destructive.
+ However, if we're in a git context, it's always the right thing to do
+ to recreate SOURCES.txt
+ """
+ manifest_filename = os.path.join(self.egg_info, "SOURCES.txt")
+ if (not os.path.exists(manifest_filename) or
+ os.path.exists('.git') or
+ 'sdist' in sys.argv):
+ log.info("[pbr] Processing SOURCES.txt")
+ mm = LocalManifestMaker(self.distribution)
+ mm.manifest = manifest_filename
+ mm.run()
+ self.filelist = mm.filelist
+ else:
+ log.info("[pbr] Reusing existing SOURCES.txt")
+ self.filelist = egg_info.FileList()
+ for entry in open(manifest_filename, 'r').read().split('\n'):
+ self.filelist.append(entry)
+
+
+def _from_git(distribution):
+ option_dict = distribution.get_option_dict('pbr')
+ changelog = git._iter_log_oneline()
+ if changelog:
+ changelog = git._iter_changelog(changelog)
+ git.write_git_changelog(option_dict=option_dict, changelog=changelog)
+ git.generate_authors(option_dict=option_dict)
+
+
+class LocalSDist(sdist.sdist):
+ """Builds the ChangeLog and Authors files from VC first."""
+
+ command_name = 'sdist'
+
+ def run(self):
+ _from_git(self.distribution)
+ # sdist.sdist is an old style class, can't use super()
+ sdist.sdist.run(self)
+
+try:
+ from pbr import builddoc
+ _have_sphinx = True
+ # Import the symbols from their new home so the package API stays
+ # compatible.
+ LocalBuildDoc = builddoc.LocalBuildDoc
+ LocalBuildLatex = builddoc.LocalBuildLatex
+except ImportError:
+ _have_sphinx = False
+ LocalBuildDoc = None
+ LocalBuildLatex = None
+
+
+def have_sphinx():
+ return _have_sphinx
+
+
+def _get_increment_kwargs(git_dir, tag):
+ """Calculate the sort of semver increment needed from git history.
+
+ Every commit from HEAD to tag is consider for Sem-Ver metadata lines.
+ See the pbr docs for their syntax.
+
+ :return: a dict of kwargs for passing into SemanticVersion.increment.
+ """
+ result = {}
+ if tag:
+ version_spec = tag + "..HEAD"
+ else:
+ version_spec = "HEAD"
+ changelog = git._run_git_command(['log', version_spec], git_dir)
+ header_len = len(' sem-ver:')
+ commands = [line[header_len:].strip() for line in changelog.split('\n')
+ if line.lower().startswith(' sem-ver:')]
+ symbols = set()
+ for command in commands:
+ symbols.update([symbol.strip() for symbol in command.split(',')])
+
+ def _handle_symbol(symbol, symbols, impact):
+ if symbol in symbols:
+ result[impact] = True
+ symbols.discard(symbol)
+ _handle_symbol('bugfix', symbols, 'patch')
+ _handle_symbol('feature', symbols, 'minor')
+ _handle_symbol('deprecation', symbols, 'minor')
+ _handle_symbol('api-break', symbols, 'major')
+ for symbol in symbols:
+ log.info('[pbr] Unknown Sem-Ver symbol %r' % symbol)
+ # We don't want patch in the kwargs since it is not a keyword argument -
+ # its the default minimum increment.
+ result.pop('patch', None)
+ return result
+
+
+def _get_revno_and_last_tag(git_dir):
+ """Return the commit data about the most recent tag.
+
+ We use git-describe to find this out, but if there are no
+ tags then we fall back to counting commits since the beginning
+ of time.
+ """
+ changelog = git._iter_log_oneline(git_dir=git_dir)
+ row_count = 0
+ for row_count, (ignored, tag_set, ignored) in enumerate(changelog):
+ version_tags = set()
+ semver_to_tag = dict()
+ for tag in list(tag_set):
+ try:
+ semver = version.SemanticVersion.from_pip_string(tag)
+ semver_to_tag[semver] = tag
+ version_tags.add(semver)
+ except Exception:
+ pass
+ if version_tags:
+ return semver_to_tag[max(version_tags)], row_count
+ return "", row_count
+
+
+def _get_version_from_git_target(git_dir, target_version):
+ """Calculate a version from a target version in git_dir.
+
+ This is used for untagged versions only. A new version is calculated as
+ necessary based on git metadata - distance to tags, current hash, contents
+ of commit messages.
+
+ :param git_dir: The git directory we're working from.
+ :param target_version: If None, the last tagged version (or 0 if there are
+ no tags yet) is incremented as needed to produce an appropriate target
+ version following semver rules. Otherwise target_version is used as a
+ constraint - if semver rules would result in a newer version then an
+ exception is raised.
+ :return: A semver version object.
+ """
+ tag, distance = _get_revno_and_last_tag(git_dir)
+ last_semver = version.SemanticVersion.from_pip_string(tag or '0')
+ if distance == 0:
+ new_version = last_semver
+ else:
+ new_version = last_semver.increment(
+ **_get_increment_kwargs(git_dir, tag))
+ if target_version is not None and new_version > target_version:
+ raise ValueError(
+ "git history requires a target version of %(new)s, but target "
+ "version is %(target)s" %
+ dict(new=new_version, target=target_version))
+ if distance == 0:
+ return last_semver
+ new_dev = new_version.to_dev(distance)
+ if target_version is not None:
+ target_dev = target_version.to_dev(distance)
+ if target_dev > new_dev:
+ return target_dev
+ return new_dev
+
+
+def _get_version_from_git(pre_version=None):
+ """Calculate a version string from git.
+
+ If the revision is tagged, return that. Otherwise calculate a semantic
+ version description of the tree.
+
+ The number of revisions since the last tag is included in the dev counter
+ in the version for untagged versions.
+
+ :param pre_version: If supplied use this as the target version rather than
+ inferring one from the last tag + commit messages.
+ """
+ git_dir = git._run_git_functions()
+ if git_dir:
+ try:
+ tagged = git._run_git_command(
+ ['describe', '--exact-match'], git_dir,
+ throw_on_error=True).replace('-', '.')
+ target_version = version.SemanticVersion.from_pip_string(tagged)
+ except Exception:
+ if pre_version:
+ # not released yet - use pre_version as the target
+ target_version = version.SemanticVersion.from_pip_string(
+ pre_version)
+ else:
+ # not released yet - just calculate from git history
+ target_version = None
+ result = _get_version_from_git_target(git_dir, target_version)
+ return result.release_string()
+ # If we don't know the version, return an empty string so at least
+ # the downstream users of the value always have the same type of
+ # object to work with.
+ try:
+ return unicode()
+ except NameError:
+ return ''
+
+
+def _get_version_from_pkg_metadata(package_name):
+ """Get the version from package metadata if present.
+
+ This looks for PKG-INFO if present (for sdists), and if not looks
+ for METADATA (for wheels) and failing that will return None.
+ """
+ pkg_metadata_filenames = ['PKG-INFO', 'METADATA']
+ pkg_metadata = {}
+ for filename in pkg_metadata_filenames:
+ try:
+ pkg_metadata_file = open(filename, 'r')
+ except (IOError, OSError):
+ continue
+ try:
+ pkg_metadata = email.message_from_file(pkg_metadata_file)
+ except email.errors.MessageError:
+ continue
+
+ # Check to make sure we're in our own dir
+ if pkg_metadata.get('Name', None) != package_name:
+ return None
+ return pkg_metadata.get('Version', None)
+
+
+def get_version(package_name, pre_version=None):
+ """Get the version of the project.
+
+ First, try getting it from PKG-INFO or METADATA, if it exists. If it does,
+ that means we're in a distribution tarball or that install has happened.
+ Otherwise, if there is no PKG-INFO or METADATA file, pull the version
+ from git.
+
+ We do not support setup.py version sanity in git archive tarballs, nor do
+ we support packagers directly sucking our git repo into theirs. We expect
+ that a source tarball be made from our git repo - or that if someone wants
+ to make a source tarball from a fork of our repo with additional tags in it
+ that they understand and desire the results of doing that.
+
+ :param pre_version: The version field from setup.cfg - if set then this
+ version will be the next release.
+ """
+ version = os.environ.get(
+ "PBR_VERSION",
+ os.environ.get("OSLO_PACKAGE_VERSION", None))
+ if version:
+ return version
+ version = _get_version_from_pkg_metadata(package_name)
+ if version:
+ return version
+ version = _get_version_from_git(pre_version)
+ # Handle http://bugs.python.org/issue11638
+ # version will either be an empty unicode string or a valid
+ # unicode version string, but either way it's unicode and needs to
+ # be encoded.
+ if sys.version_info[0] == 2:
+ version = version.encode('utf-8')
+ if version:
+ return version
+ raise Exception("Versioning for this project requires either an sdist"
+ " tarball, or access to an upstream git repository."
+ " It's also possible that there is a mismatch between"
+ " the package name in setup.cfg and the argument given"
+ " to pbr.version.VersionInfo. Project name {name} was"
+ " given, but was not able to be found.".format(
+ name=package_name))
+
+
+# This is added because pbr uses pbr to install itself. That means that
+# any changes to the egg info writer entrypoints must be forward and
+# backward compatible. This maintains the pbr.packaging.write_pbr_json
+# path.
+write_pbr_json = pbr.pbr_json.write_pbr_json
--- /dev/null
+# Copyright 2011 OpenStack Foundation
+# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+
+from pbr import git
+
+
+def write_pbr_json(cmd, basename, filename):
+ if not hasattr(cmd.distribution, 'pbr') or not cmd.distribution.pbr:
+ return
+ git_dir = git._run_git_functions()
+ if not git_dir:
+ return
+ values = dict()
+ git_version = git.get_git_short_sha(git_dir)
+ is_release = git.get_is_release(git_dir)
+ if git_version is not None:
+ values['git_version'] = git_version
+ values['is_release'] = is_release
+ cmd.write_file('pbr', filename, json.dumps(values, sort_keys=True))
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (c) 2013 Testrepository Contributors
+#
+# Licensed under either the Apache License, Version 2.0 or the BSD 3-clause
+# license at the users choice. A copy of both licenses are available in the
+# project source as Apache-2.0 and BSD. You may not use this file except in
+# compliance with one of these two licences.
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# license you chose for the specific language governing permissions and
+# limitations under that license.
+
+"""setuptools/distutils commands to run testr via setup.py
+
+Currently provides 'testr' which runs tests using testr. You can pass
+--coverage which will also export PYTHON='coverage run --source <your package>'
+and automatically combine the coverage from each testr backend test runner
+after the run completes.
+
+To use, just use setuptools/distribute and depend on testr, and it should be
+picked up automatically (as the commands are exported in the testrepository
+package metadata.
+"""
+
+from distutils import cmd
+import distutils.errors
+import logging
+import os
+import sys
+
+logger = logging.getLogger(__name__)
+
+
+class TestrReal(cmd.Command):
+
+ description = "Run unit tests using testr"
+
+ user_options = [
+ ('coverage', None, "Replace PYTHON with coverage and merge coverage "
+ "from each testr worker."),
+ ('testr-args=', 't', "Run 'testr' with these args"),
+ ('omit=', 'o', "Files to omit from coverage calculations"),
+ ('coverage-package-name=', None, "Use this name to select packages "
+ "for coverage (one or more, "
+ "comma-separated)"),
+ ('slowest', None, "Show slowest test times after tests complete."),
+ ('no-parallel', None, "Run testr serially"),
+ ('log-level=', 'l', "Log level (default: info)"),
+ ]
+
+ boolean_options = ['coverage', 'slowest', 'no_parallel']
+
+ def _run_testr(self, *args):
+ logger.debug("_run_testr called with args = %r", args)
+ return commands.run_argv([sys.argv[0]] + list(args),
+ sys.stdin, sys.stdout, sys.stderr)
+
+ def initialize_options(self):
+ self.testr_args = None
+ self.coverage = None
+ self.omit = ""
+ self.slowest = None
+ self.coverage_package_name = None
+ self.no_parallel = None
+ self.log_level = 'info'
+
+ def finalize_options(self):
+ self.log_level = getattr(
+ logging,
+ self.log_level.upper(),
+ logging.INFO)
+ logging.basicConfig(level=self.log_level)
+ logger.debug("finalize_options called")
+ if self.testr_args is None:
+ self.testr_args = []
+ else:
+ self.testr_args = self.testr_args.split()
+ if self.omit:
+ self.omit = "--omit=%s" % self.omit
+ logger.debug("finalize_options: self.__dict__ = %r", self.__dict__)
+
+ def run(self):
+ """Set up testr repo, then run testr."""
+ logger.debug("run called")
+ if not os.path.isdir(".testrepository"):
+ self._run_testr("init")
+
+ if self.coverage:
+ self._coverage_before()
+ if not self.no_parallel:
+ testr_ret = self._run_testr("run", "--parallel", *self.testr_args)
+ else:
+ testr_ret = self._run_testr("run", *self.testr_args)
+ if testr_ret:
+ raise distutils.errors.DistutilsError(
+ "testr failed (%d)" % testr_ret)
+ if self.slowest:
+ print("Slowest Tests")
+ self._run_testr("slowest")
+ if self.coverage:
+ self._coverage_after()
+
+ def _coverage_before(self):
+ logger.debug("_coverage_before called")
+ package = self.distribution.get_name()
+ if package.startswith('python-'):
+ package = package[7:]
+
+ # Use this as coverage package name
+ if self.coverage_package_name:
+ package = self.coverage_package_name
+ options = "--source %s --parallel-mode" % package
+ os.environ['PYTHON'] = ("coverage run %s" % options)
+ logger.debug("os.environ['PYTHON'] = %r", os.environ['PYTHON'])
+
+ def _coverage_after(self):
+ logger.debug("_coverage_after called")
+ os.system("coverage combine")
+ os.system("coverage html -d ./cover %s" % self.omit)
+
+
+class TestrFake(cmd.Command):
+ description = "Run unit tests using testr"
+ user_options = []
+
+ def initialize_options(self):
+ pass
+
+ def finalize_options(self):
+ pass
+
+ def run(self):
+ print("Install testrepository to run 'testr' command properly.")
+
+
+try:
+ from testrepository import commands
+ have_testr = True
+ Testr = TestrReal
+except ImportError:
+ have_testr = False
+ Testr = TestrFake
--- /dev/null
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+
+import testscenarios
+
+
+def load_tests(loader, standard_tests, pattern):
+ # top level directory cached on loader instance
+ this_dir = os.path.dirname(__file__)
+ package_tests = loader.discover(start_dir=this_dir, pattern=pattern)
+ result = loader.suiteClass()
+ result.addTests(testscenarios.generate_scenarios(standard_tests))
+ result.addTests(testscenarios.generate_scenarios(package_tests))
+ return result
--- /dev/null
+# Copyright 2010-2011 OpenStack Foundation
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+
+"""Common utilities used in testing"""
+
+import os
+import shutil
+import subprocess
+import sys
+
+import fixtures
+import testresources
+import testtools
+from testtools import content
+
+from pbr import options
+
+
+class DiveDir(fixtures.Fixture):
+ """Dive into given directory and return back on cleanup.
+
+ :ivar path: The target directory.
+ """
+
+ def __init__(self, path):
+ self.path = path
+
+ def setUp(self):
+ super(DiveDir, self).setUp()
+ self.addCleanup(os.chdir, os.getcwd())
+ os.chdir(self.path)
+
+
+class BaseTestCase(testtools.TestCase, testresources.ResourcedTestCase):
+
+ def setUp(self):
+ super(BaseTestCase, self).setUp()
+ test_timeout = os.environ.get('OS_TEST_TIMEOUT', 30)
+ try:
+ test_timeout = int(test_timeout)
+ except ValueError:
+ # If timeout value is invalid, fail hard.
+ print("OS_TEST_TIMEOUT set to invalid value"
+ " defaulting to no timeout")
+ test_timeout = 0
+ if test_timeout > 0:
+ self.useFixture(fixtures.Timeout(test_timeout, gentle=True))
+
+ if os.environ.get('OS_STDOUT_CAPTURE') in options.TRUE_VALUES:
+ stdout = self.useFixture(fixtures.StringStream('stdout')).stream
+ self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
+ if os.environ.get('OS_STDERR_CAPTURE') in options.TRUE_VALUES:
+ stderr = self.useFixture(fixtures.StringStream('stderr')).stream
+ self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
+ self.log_fixture = self.useFixture(
+ fixtures.FakeLogger('pbr'))
+
+ # Older git does not have config --local, so create a temporary home
+ # directory to permit using git config --global without stepping on
+ # developer configuration.
+ self.useFixture(fixtures.TempHomeDir())
+ self.useFixture(fixtures.NestedTempfile())
+ self.useFixture(fixtures.FakeLogger())
+ # TODO(lifeless) we should remove PBR_VERSION from the environment.
+ # rather than setting it, because thats not representative - we need to
+ # test non-preversioned codepaths too!
+ self.useFixture(fixtures.EnvironmentVariable('PBR_VERSION', '0.0'))
+
+ self.temp_dir = self.useFixture(fixtures.TempDir()).path
+ self.package_dir = os.path.join(self.temp_dir, 'testpackage')
+ shutil.copytree(os.path.join(os.path.dirname(__file__), 'testpackage'),
+ self.package_dir)
+ self.addCleanup(os.chdir, os.getcwd())
+ os.chdir(self.package_dir)
+ self.addCleanup(self._discard_testpackage)
+ # Tests can opt into non-PBR_VERSION by setting preversioned=False as
+ # an attribute.
+ if not getattr(self, 'preversioned', True):
+ self.useFixture(fixtures.EnvironmentVariable('PBR_VERSION'))
+ setup_cfg_path = os.path.join(self.package_dir, 'setup.cfg')
+ with open(setup_cfg_path, 'rt') as cfg:
+ content = cfg.read()
+ content = content.replace(u'version = 0.1.dev', u'')
+ with open(setup_cfg_path, 'wt') as cfg:
+ cfg.write(content)
+
+ def _discard_testpackage(self):
+ # Remove pbr.testpackage from sys.modules so that it can be freshly
+ # re-imported by the next test
+ for k in list(sys.modules):
+ if (k == 'pbr_testpackage' or
+ k.startswith('pbr_testpackage.')):
+ del sys.modules[k]
+
+ def run_pbr(self, *args, **kwargs):
+ return self._run_cmd('pbr', args, **kwargs)
+
+ def run_setup(self, *args, **kwargs):
+ return self._run_cmd(sys.executable, ('setup.py',) + args, **kwargs)
+
+ def _run_cmd(self, cmd, args=[], allow_fail=True, cwd=None):
+ """Run a command in the root of the test working copy.
+
+ Runs a command, with the given argument list, in the root of the test
+ working copy--returns the stdout and stderr streams and the exit code
+ from the subprocess.
+
+ :param cwd: If falsy run within the test package dir, otherwise run
+ within the named path.
+ """
+ cwd = cwd or self.package_dir
+ result = _run_cmd([cmd] + list(args), cwd=cwd)
+ if result[2] and not allow_fail:
+ raise Exception("Command failed retcode=%s" % result[2])
+ return result
+
+
+class CapturedSubprocess(fixtures.Fixture):
+ """Run a process and capture its output.
+
+ :attr stdout: The output (a string).
+ :attr stderr: The standard error (a string).
+ :attr returncode: The return code of the process.
+
+ Note that stdout and stderr are decoded from the bytestrings subprocess
+ returns using error=replace.
+ """
+
+ def __init__(self, label, *args, **kwargs):
+ """Create a CapturedSubprocess.
+
+ :param label: A label for the subprocess in the test log. E.g. 'foo'.
+ :param *args: The *args to pass to Popen.
+ :param **kwargs: The **kwargs to pass to Popen.
+ """
+ super(CapturedSubprocess, self).__init__()
+ self.label = label
+ self.args = args
+ self.kwargs = kwargs
+ self.kwargs['stderr'] = subprocess.PIPE
+ self.kwargs['stdin'] = subprocess.PIPE
+ self.kwargs['stdout'] = subprocess.PIPE
+
+ def setUp(self):
+ super(CapturedSubprocess, self).setUp()
+ proc = subprocess.Popen(*self.args, **self.kwargs)
+ out, err = proc.communicate()
+ self.out = out.decode('utf-8', 'replace')
+ self.err = err.decode('utf-8', 'replace')
+ self.addDetail(self.label + '-stdout', content.text_content(self.out))
+ self.addDetail(self.label + '-stderr', content.text_content(self.err))
+ self.returncode = proc.returncode
+ if proc.returncode:
+ raise AssertionError('Failed process %s' % proc.returncode)
+ self.addCleanup(delattr, self, 'out')
+ self.addCleanup(delattr, self, 'err')
+ self.addCleanup(delattr, self, 'returncode')
+
+
+def _run_cmd(args, cwd):
+ """Run the command args in cwd.
+
+ :param args: The command to run e.g. ['git', 'status']
+ :param cwd: The directory to run the comamnd in.
+ :return: ((stdout, stderr), returncode)
+ """
+ p = subprocess.Popen(
+ args, stdin=subprocess.PIPE, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, cwd=cwd)
+ streams = tuple(s.decode('latin1').strip() for s in p.communicate())
+ for stream_content in streams:
+ print(stream_content)
+ return (streams) + (p.returncode,)
+
+
+def _config_git():
+ _run_cmd(
+ ['git', 'config', '--global', 'user.email', 'example@example.com'],
+ None)
+ _run_cmd(
+ ['git', 'config', '--global', 'user.name', 'OpenStack Developer'],
+ None)
+ _run_cmd(
+ ['git', 'config', '--global', 'user.signingkey',
+ 'example@example.com'], None)
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+
+from testtools import content
+
+from pbr.tests import base
+
+
+class TestCommands(base.BaseTestCase):
+ def test_custom_build_py_command(self):
+ """Test custom build_py command.
+
+ Test that a custom subclass of the build_py command runs when listed in
+ the commands [global] option, rather than the normal build command.
+ """
+
+ stdout, stderr, return_code = self.run_setup('build_py')
+ self.addDetail('stdout', content.text_content(stdout))
+ self.addDetail('stderr', content.text_content(stderr))
+ self.assertIn('Running custom build_py command.', stdout)
+ self.assertEqual(0, return_code)
+
+ def test_custom_deb_version_py_command(self):
+ """Test custom deb_version command."""
+ stdout, stderr, return_code = self.run_setup('deb_version')
+ self.addDetail('stdout', content.text_content(stdout))
+ self.addDetail('stderr', content.text_content(stderr))
+ self.assertIn('Extracting deb version', stdout)
+ self.assertEqual(0, return_code)
+
+ def test_custom_rpm_version_py_command(self):
+ """Test custom rpm_version command."""
+ stdout, stderr, return_code = self.run_setup('rpm_version')
+ self.addDetail('stdout', content.text_content(stdout))
+ self.addDetail('stderr', content.text_content(stderr))
+ self.assertIn('Extracting rpm version', stdout)
+ self.assertEqual(0, return_code)
+
+ def test_freeze_command(self):
+ """Test that freeze output is sorted in a case-insensitive manner."""
+ stdout, stderr, return_code = self.run_pbr('freeze')
+ self.assertEqual(0, return_code)
+ pkgs = []
+ for l in stdout.split('\n'):
+ pkgs.append(l.split('==')[0].lower())
+ pkgs_sort = sorted(pkgs[:])
+ self.assertEqual(pkgs_sort, pkgs)
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+
+import glob
+import os
+import tarfile
+
+import fixtures
+
+from pbr.tests import base
+
+
+class TestCore(base.BaseTestCase):
+
+ cmd_names = ('pbr_test_cmd', 'pbr_test_cmd_with_class')
+
+ def check_script_install(self, install_stdout):
+ for cmd_name in self.cmd_names:
+ install_txt = 'Installing %s script to %s' % (cmd_name,
+ self.temp_dir)
+ self.assertIn(install_txt, install_stdout)
+
+ cmd_filename = os.path.join(self.temp_dir, cmd_name)
+
+ script_txt = open(cmd_filename, 'r').read()
+ self.assertNotIn('pkg_resources', script_txt)
+
+ stdout, _, return_code = self._run_cmd(cmd_filename)
+ self.assertIn("PBR", stdout)
+
+ def test_setup_py_keywords(self):
+ """setup.py --keywords.
+
+ Test that the `./setup.py --keywords` command returns the correct
+ value without balking.
+ """
+
+ self.run_setup('egg_info')
+ stdout, _, _ = self.run_setup('--keywords')
+ assert stdout == 'packaging,distutils,setuptools'
+
+ def test_setup_py_build_sphinx(self):
+ stdout, _, return_code = self.run_setup('build_sphinx')
+ self.assertEqual(0, return_code)
+
+ def test_sdist_extra_files(self):
+ """Test that the extra files are correctly added."""
+
+ stdout, _, return_code = self.run_setup('sdist', '--formats=gztar')
+
+ # There can be only one
+ try:
+ tf_path = glob.glob(os.path.join('dist', '*.tar.gz'))[0]
+ except IndexError:
+ assert False, 'source dist not found'
+
+ tf = tarfile.open(tf_path)
+ names = ['/'.join(p.split('/')[1:]) for p in tf.getnames()]
+
+ self.assertIn('extra-file.txt', names)
+
+ def test_console_script_install(self):
+ """Test that we install a non-pkg-resources console script."""
+
+ if os.name == 'nt':
+ self.skipTest('Windows support is passthrough')
+
+ stdout, _, return_code = self.run_setup(
+ 'install_scripts', '--install-dir=%s' % self.temp_dir)
+
+ self.useFixture(
+ fixtures.EnvironmentVariable('PYTHONPATH', '.'))
+
+ self.check_script_install(stdout)
+
+ def test_console_script_develop(self):
+ """Test that we develop a non-pkg-resources console script."""
+
+ if os.name == 'nt':
+ self.skipTest('Windows support is passthrough')
+
+ self.useFixture(
+ fixtures.EnvironmentVariable(
+ 'PYTHONPATH', ".:%s" % self.temp_dir))
+
+ stdout, _, return_code = self.run_setup(
+ 'develop', '--install-dir=%s' % self.temp_dir)
+
+ self.check_script_install(stdout)
+
+
+class TestGitSDist(base.BaseTestCase):
+
+ def setUp(self):
+ super(TestGitSDist, self).setUp()
+
+ stdout, _, return_code = self._run_cmd('git', ('init',))
+ if return_code:
+ self.skipTest("git not installed")
+
+ stdout, _, return_code = self._run_cmd('git', ('add', '.'))
+ stdout, _, return_code = self._run_cmd(
+ 'git', ('commit', '-m', 'Turn this into a git repo'))
+
+ stdout, _, return_code = self.run_setup('sdist', '--formats=gztar')
+
+ def test_sdist_git_extra_files(self):
+ """Test that extra files found in git are correctly added."""
+ # There can be only one
+ tf_path = glob.glob(os.path.join('dist', '*.tar.gz'))[0]
+ tf = tarfile.open(tf_path)
+ names = ['/'.join(p.split('/')[1:]) for p in tf.getnames()]
+
+ self.assertIn('git-extra-file.txt', names)
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from __future__ import print_function
+
+import os
+
+import fixtures
+
+from pbr.hooks import files
+from pbr.tests import base
+
+
+class FilesConfigTest(base.BaseTestCase):
+
+ def setUp(self):
+ super(FilesConfigTest, self).setUp()
+
+ pkg_fixture = fixtures.PythonPackage(
+ "fake_package", [
+ ("fake_module.py", b""),
+ ("other_fake_module.py", b""),
+ ])
+ self.useFixture(pkg_fixture)
+ pkg_etc = os.path.join(pkg_fixture.base, 'etc')
+ pkg_sub = os.path.join(pkg_etc, 'sub')
+ subpackage = os.path.join(
+ pkg_fixture.base, 'fake_package', 'subpackage')
+ os.makedirs(pkg_sub)
+ os.makedirs(subpackage)
+ with open(os.path.join(pkg_etc, "foo"), 'w') as foo_file:
+ foo_file.write("Foo Data")
+ with open(os.path.join(pkg_sub, "bar"), 'w') as foo_file:
+ foo_file.write("Bar Data")
+ with open(os.path.join(subpackage, "__init__.py"), 'w') as foo_file:
+ foo_file.write("# empty")
+
+ self.useFixture(base.DiveDir(pkg_fixture.base))
+
+ def test_implicit_auto_package(self):
+ config = dict(
+ files=dict(
+ )
+ )
+ files.FilesConfig(config, 'fake_package').run()
+ self.assertIn('subpackage', config['files']['packages'])
+
+ def test_auto_package(self):
+ config = dict(
+ files=dict(
+ packages='fake_package',
+ )
+ )
+ files.FilesConfig(config, 'fake_package').run()
+ self.assertIn('subpackage', config['files']['packages'])
+
+ def test_data_files_globbing(self):
+ config = dict(
+ files=dict(
+ data_files="\n etc/pbr = etc/*"
+ )
+ )
+ files.FilesConfig(config, 'fake_package').run()
+ self.assertIn(
+ '\netc/pbr/ = \n etc/foo\netc/pbr/sub = \n etc/sub/bar',
+ config['files']['data_files'])
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+
+import os
+import textwrap
+
+from testtools import content
+from testtools import matchers
+
+from pbr.tests import base
+from pbr.tests import util
+
+
+class TestHooks(base.BaseTestCase):
+ def setUp(self):
+ super(TestHooks, self).setUp()
+ with util.open_config(
+ os.path.join(self.package_dir, 'setup.cfg')) as cfg:
+ cfg.set('global', 'setup-hooks',
+ 'pbr_testpackage._setup_hooks.test_hook_1\n'
+ 'pbr_testpackage._setup_hooks.test_hook_2')
+ cfg.set('build_ext', 'pre-hook.test_pre_hook',
+ 'pbr_testpackage._setup_hooks.test_pre_hook')
+ cfg.set('build_ext', 'post-hook.test_post_hook',
+ 'pbr_testpackage._setup_hooks.test_post_hook')
+
+ def test_global_setup_hooks(self):
+ """Test setup_hooks.
+
+ Test that setup_hooks listed in the [global] section of setup.cfg are
+ executed in order.
+ """
+
+ stdout, _, return_code = self.run_setup('egg_info')
+ assert 'test_hook_1\ntest_hook_2' in stdout
+ assert return_code == 0
+
+ def test_command_hooks(self):
+ """Test command hooks.
+
+ Simple test that the appropriate command hooks run at the
+ beginning/end of the appropriate command.
+ """
+
+ stdout, _, return_code = self.run_setup('egg_info')
+ assert 'build_ext pre-hook' not in stdout
+ assert 'build_ext post-hook' not in stdout
+ assert return_code == 0
+
+ stdout, stderr, return_code = self.run_setup('build_ext')
+ self.addDetailUniqueName('stderr', content.text_content(stderr))
+ assert textwrap.dedent("""
+ running build_ext
+ running pre_hook pbr_testpackage._setup_hooks.test_pre_hook for command build_ext
+ build_ext pre-hook
+ """) in stdout # flake8: noqa
+ self.expectThat(stdout, matchers.EndsWith('build_ext post-hook'))
+ assert return_code == 0
+
+ def test_custom_commands_known(self):
+ stdout, _, return_code = self.run_setup('--help-commands')
+ self.assertFalse(return_code)
+ self.assertThat(stdout, matchers.Contains(" testr "))
--- /dev/null
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os.path
+import shlex
+import sys
+
+import fixtures
+import testtools
+import textwrap
+
+from pbr.tests import base
+from pbr.tests import test_packaging
+
+PIPFLAGS = shlex.split(os.environ.get('PIPFLAGS', ''))
+PIPVERSION = os.environ.get('PIPVERSION', 'pip')
+PBRVERSION = os.environ.get('PBRVERSION', 'pbr')
+REPODIR = os.environ.get('REPODIR', '')
+WHEELHOUSE = os.environ.get('WHEELHOUSE', '')
+PIP_CMD = ['-m', 'pip'] + PIPFLAGS + ['install', '-f', WHEELHOUSE]
+PROJECTS = shlex.split(os.environ.get('PROJECTS', ''))
+PBR_ROOT = os.path.abspath(os.path.join(__file__, '..', '..', '..'))
+
+
+def all_projects():
+ if not REPODIR:
+ return
+ # Future: make this path parameterisable.
+ excludes = set(['tempest', 'requirements'])
+ for name in PROJECTS:
+ name = name.strip()
+ short_name = name.split('/')[-1]
+ try:
+ with open(os.path.join(
+ REPODIR, short_name, 'setup.py'), 'rt') as f:
+ if 'pbr' not in f.read():
+ continue
+ except IOError:
+ continue
+ if short_name in excludes:
+ continue
+ yield (short_name, dict(name=name, short_name=short_name))
+
+
+class TestIntegration(base.BaseTestCase):
+
+ scenarios = list(all_projects())
+
+ def setUp(self):
+ # Integration tests need a higher default - big repos can be slow to
+ # clone, particularly under guest load.
+ env = fixtures.EnvironmentVariable(
+ 'OS_TEST_TIMEOUT', os.environ.get('OS_TEST_TIMEOUT', '600'))
+ with env:
+ super(TestIntegration, self).setUp()
+ base._config_git()
+
+ @testtools.skipUnless(
+ os.environ.get('PBR_INTEGRATION', None) == '1',
+ 'integration tests not enabled')
+ def test_integration(self):
+ # Test that we can:
+ # - run sdist from the repo in a venv
+ # - install the resulting tarball in a new venv
+ # - pip install the repo
+ # - pip install -e the repo
+ # We don't break these into separate tests because we'd need separate
+ # source dirs to isolate from side effects of running pip, and the
+ # overheads of setup would start to beat the benefits of parallelism.
+ self.useFixture(base.CapturedSubprocess(
+ 'sync-req',
+ ['python', 'update.py', os.path.join(REPODIR, self.short_name)],
+ cwd=os.path.join(REPODIR, 'requirements')))
+ self.useFixture(base.CapturedSubprocess(
+ 'commit-requirements',
+ 'git diff --quiet || git commit -amrequirements',
+ cwd=os.path.join(REPODIR, self.short_name), shell=True))
+ path = os.path.join(
+ self.useFixture(fixtures.TempDir()).path, 'project')
+ self.useFixture(base.CapturedSubprocess(
+ 'clone',
+ ['git', 'clone', os.path.join(REPODIR, self.short_name), path]))
+ venv = self.useFixture(
+ test_packaging.Venv('sdist',
+ modules=['pip', 'wheel', PBRVERSION],
+ pip_cmd=PIP_CMD))
+ python = venv.python
+ self.useFixture(base.CapturedSubprocess(
+ 'sdist', [python, 'setup.py', 'sdist'], cwd=path))
+ venv = self.useFixture(
+ test_packaging.Venv('tarball',
+ modules=['pip', 'wheel', PBRVERSION],
+ pip_cmd=PIP_CMD))
+ python = venv.python
+ filename = os.path.join(
+ path, 'dist', os.listdir(os.path.join(path, 'dist'))[0])
+ self.useFixture(base.CapturedSubprocess(
+ 'tarball', [python] + PIP_CMD + [filename]))
+ venv = self.useFixture(
+ test_packaging.Venv('install-git',
+ modules=['pip', 'wheel', PBRVERSION],
+ pip_cmd=PIP_CMD))
+ root = venv.path
+ python = venv.python
+ self.useFixture(base.CapturedSubprocess(
+ 'install-git', [python] + PIP_CMD + ['git+file://' + path]))
+ if self.short_name == 'nova':
+ found = False
+ for _, _, filenames in os.walk(root):
+ if 'migrate.cfg' in filenames:
+ found = True
+ self.assertTrue(found)
+ venv = self.useFixture(
+ test_packaging.Venv('install-e',
+ modules=['pip', 'wheel', PBRVERSION],
+ pip_cmd=PIP_CMD))
+ root = venv.path
+ python = venv.python
+ self.useFixture(base.CapturedSubprocess(
+ 'install-e', [python] + PIP_CMD + ['-e', path]))
+
+
+class TestInstallWithoutPbr(base.BaseTestCase):
+
+ @testtools.skipUnless(
+ os.environ.get('PBR_INTEGRATION', None) == '1',
+ 'integration tests not enabled')
+ def test_install_without_pbr(self):
+ # Test easy-install of a thing that depends on a thing using pbr
+ tempdir = self.useFixture(fixtures.TempDir()).path
+ # A directory containing sdists of the things we're going to depend on
+ # in using-package.
+ dist_dir = os.path.join(tempdir, 'distdir')
+ os.mkdir(dist_dir)
+ self._run_cmd(sys.executable, ('setup.py', 'sdist', '-d', dist_dir),
+ allow_fail=False, cwd=PBR_ROOT)
+ # testpkg - this requires a pbr-using package
+ test_pkg_dir = os.path.join(tempdir, 'testpkg')
+ os.mkdir(test_pkg_dir)
+ pkgs = {
+ 'pkgTest': {
+ 'setup.py': textwrap.dedent("""\
+ #!/usr/bin/env python
+ import setuptools
+ setuptools.setup(
+ name = 'pkgTest',
+ tests_require = ['pkgReq'],
+ test_suite='pkgReq'
+ )
+ """),
+ 'setup.cfg': textwrap.dedent("""\
+ [easy_install]
+ find_links = %s
+ """ % dist_dir)},
+ 'pkgReq': {
+ 'requirements.txt': textwrap.dedent("""\
+ pbr
+ """),
+ 'pkgReq/__init__.py': textwrap.dedent("""\
+ print("FakeTest loaded and ran")
+ """)},
+ }
+ pkg_dirs = self.useFixture(
+ test_packaging.CreatePackages(pkgs)).package_dirs
+ test_pkg_dir = pkg_dirs['pkgTest']
+ req_pkg_dir = pkg_dirs['pkgReq']
+
+ self._run_cmd(sys.executable, ('setup.py', 'sdist', '-d', dist_dir),
+ allow_fail=False, cwd=req_pkg_dir)
+ # A venv to test within
+ venv = self.useFixture(test_packaging.Venv('nopbr', ['pip', 'wheel']))
+ python = venv.python
+ # Run the depending script
+ self.useFixture(base.CapturedSubprocess(
+ 'nopbr', [python] + ['setup.py', 'test'], cwd=test_pkg_dir))
+
+
+class TestMarkersPip(base.BaseTestCase):
+
+ scenarios = [
+ ('pip-1.5', {'modules': ['pip>=1.5,<1.6']}),
+ ('pip-6.0', {'modules': ['pip>=6.0,<6.1']}),
+ ('pip-latest', {'modules': ['pip']}),
+ ('setuptools-EL7', {'modules': ['pip==1.4.1', 'setuptools==0.9.8']}),
+ ('setuptools-Trusty', {'modules': ['pip==1.5', 'setuptools==2.2']}),
+ ('setuptools-minimum', {'modules': ['pip==1.5', 'setuptools==0.7.2']}),
+ ]
+
+ @testtools.skipUnless(
+ os.environ.get('PBR_INTEGRATION', None) == '1',
+ 'integration tests not enabled')
+ def test_pip_versions(self):
+ pkgs = {
+ 'test_markers':
+ {'requirements.txt': textwrap.dedent("""\
+ pkg_a; python_version=='1.2'
+ pkg_b; python_version!='1.2'
+ """)},
+ 'pkg_a': {},
+ 'pkg_b': {},
+ }
+ pkg_dirs = self.useFixture(
+ test_packaging.CreatePackages(pkgs)).package_dirs
+ temp_dir = self.useFixture(fixtures.TempDir()).path
+ repo_dir = os.path.join(temp_dir, 'repo')
+ venv = self.useFixture(test_packaging.Venv('markers'))
+ bin_python = venv.python
+ os.mkdir(repo_dir)
+ for module in self.modules:
+ self._run_cmd(
+ bin_python,
+ ['-m', 'pip', 'install', '--upgrade', module],
+ cwd=venv.path, allow_fail=False)
+ for pkg in pkg_dirs:
+ self._run_cmd(
+ bin_python, ['setup.py', 'sdist', '-d', repo_dir],
+ cwd=pkg_dirs[pkg], allow_fail=False)
+ self._run_cmd(
+ bin_python,
+ ['-m', 'pip', 'install', '--no-index', '-f', repo_dir,
+ 'test_markers'],
+ cwd=venv.path, allow_fail=False)
+ self.assertIn('pkg-b', self._run_cmd(
+ bin_python, ['-m', 'pip', 'freeze'], cwd=venv.path,
+ allow_fail=False)[0])
+
+
+class TestLTSSupport(base.BaseTestCase):
+
+ # These versions come from the versions installed from the 'virtualenv'
+ # command from the 'python-virtualenv' package.
+ scenarios = [
+ ('EL7', {'modules': ['pip==1.4.1', 'setuptools==0.9.8'],
+ 'py3support': True}), # And EPEL6
+ ('Trusty', {'modules': ['pip==1.5', 'setuptools==2.2'],
+ 'py3support': True}),
+ ('Jessie', {'modules': ['pip==1.5.6', 'setuptools==5.5.1'],
+ 'py3support': True}),
+ # Wheezy has pip1.1, which cannot be called with '-m pip'
+ # So we'll use a different version of pip here.
+ ('WheezyPrecise', {'modules': ['pip==1.4.1', 'setuptools==0.6c11'],
+ 'py3support': False})
+ ]
+
+ @testtools.skipUnless(
+ os.environ.get('PBR_INTEGRATION', None) == '1',
+ 'integration tests not enabled')
+ def test_lts_venv_default_versions(self):
+ if (sys.version_info[0] == 3 and not self.py3support):
+ self.skipTest('This combination will not install with py3, '
+ 'skipping test')
+ venv = self.useFixture(
+ test_packaging.Venv('setuptools', modules=self.modules))
+ bin_python = venv.python
+ pbr = 'file://%s#egg=pbr' % PBR_ROOT
+ # Installing PBR is a reasonable indication that we are not broken on
+ # this particular combination of setuptools and pip.
+ self._run_cmd(bin_python, ['-m', 'pip', 'install', pbr],
+ cwd=venv.path, allow_fail=False)
--- /dev/null
+# Copyright (c) 2013 New Dream Network, LLC (DreamHost)
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+
+import email
+import email.errors
+import imp
+import os
+import re
+import sysconfig
+import tempfile
+import textwrap
+
+import fixtures
+import mock
+import pkg_resources
+import six
+import testtools
+from testtools import matchers
+import virtualenv
+import wheel.install
+
+from pbr import git
+from pbr import packaging
+from pbr.tests import base
+
+
+PBR_ROOT = os.path.abspath(os.path.join(__file__, '..', '..', '..'))
+
+
+class TestRepo(fixtures.Fixture):
+ """A git repo for testing with.
+
+ Use of TempHomeDir with this fixture is strongly recommended as due to the
+ lack of config --local in older gits, it will write to the users global
+ configuration without TempHomeDir.
+ """
+
+ def __init__(self, basedir):
+ super(TestRepo, self).__init__()
+ self._basedir = basedir
+
+ def setUp(self):
+ super(TestRepo, self).setUp()
+ base._run_cmd(['git', 'init', '.'], self._basedir)
+ base._config_git()
+ base._run_cmd(['git', 'add', '.'], self._basedir)
+
+ def commit(self, message_content='test commit'):
+ files = len(os.listdir(self._basedir))
+ path = self._basedir + '/%d' % files
+ open(path, 'wt').close()
+ base._run_cmd(['git', 'add', path], self._basedir)
+ base._run_cmd(['git', 'commit', '-m', message_content], self._basedir)
+
+ def uncommit(self):
+ base._run_cmd(['git', 'reset', '--hard', 'HEAD^'], self._basedir)
+
+ def tag(self, version):
+ base._run_cmd(
+ ['git', 'tag', '-sm', 'test tag', version], self._basedir)
+
+
+class GPGKeyFixture(fixtures.Fixture):
+ """Creates a GPG key for testing.
+
+ It's recommended that this be used in concert with a unique home
+ directory.
+ """
+
+ def setUp(self):
+ super(GPGKeyFixture, self).setUp()
+ tempdir = self.useFixture(fixtures.TempDir())
+ gnupg_version_re = re.compile('^gpg\s.*\s([\d+])\.([\d+])\.([\d+])')
+ gnupg_version = base._run_cmd(['gpg', '--version'], tempdir.path)
+ for line in gnupg_version[0].split('\n'):
+ gnupg_version = gnupg_version_re.match(line)
+ if gnupg_version:
+ gnupg_version = (int(gnupg_version.group(1)),
+ int(gnupg_version.group(2)),
+ int(gnupg_version.group(3)))
+ break
+ else:
+ if gnupg_version is None:
+ gnupg_version = (0, 0, 0)
+ config_file = tempdir.path + '/key-config'
+ f = open(config_file, 'wt')
+ try:
+ if gnupg_version[0] == 2 and gnupg_version[1] >= 1:
+ f.write("""
+ %no-protection
+ %transient-key
+ """)
+ f.write("""
+ %no-ask-passphrase
+ Key-Type: RSA
+ Name-Real: Example Key
+ Name-Comment: N/A
+ Name-Email: example@example.com
+ Expire-Date: 2d
+ Preferences: (setpref)
+ %commit
+ """)
+ finally:
+ f.close()
+ # Note that --quick-random (--debug-quick-random in GnuPG 2.x)
+ # does not have a corresponding preferences file setting and
+ # must be passed explicitly on the command line instead
+ if gnupg_version[0] == 1:
+ gnupg_random = '--quick-random'
+ elif gnupg_version[0] >= 2:
+ gnupg_random = '--debug-quick-random'
+ else:
+ gnupg_random = ''
+ base._run_cmd(
+ ['gpg', '--gen-key', '--batch', gnupg_random, config_file],
+ tempdir.path)
+
+
+class Venv(fixtures.Fixture):
+ """Create a virtual environment for testing with.
+
+ :attr path: The path to the environment root.
+ :attr python: The path to the python binary in the environment.
+ """
+
+ def __init__(self, reason, modules=(), pip_cmd=None):
+ """Create a Venv fixture.
+
+ :param reason: A human readable string to bake into the venv
+ file path to aid diagnostics in the case of failures.
+ :param modules: A list of modules to install, defaults to latest
+ pip, wheel, and the working copy of PBR.
+ :attr pip_cmd: A list to override the default pip_cmd passed to
+ python for installing base packages.
+ """
+ self._reason = reason
+ if modules == ():
+ pbr = 'file://%s#egg=pbr' % PBR_ROOT
+ modules = ['pip', 'wheel', pbr]
+ self.modules = modules
+ if pip_cmd is None:
+ self.pip_cmd = ['-m', 'pip', 'install']
+ else:
+ self.pip_cmd = pip_cmd
+
+ def _setUp(self):
+ path = self.useFixture(fixtures.TempDir()).path
+ virtualenv.create_environment(path, clear=True)
+ python = os.path.join(path, 'bin', 'python')
+ command = [python] + self.pip_cmd + ['-U']
+ if self.modules and len(self.modules) > 0:
+ command.extend(self.modules)
+ self.useFixture(base.CapturedSubprocess(
+ 'mkvenv-' + self._reason, command))
+ self.addCleanup(delattr, self, 'path')
+ self.addCleanup(delattr, self, 'python')
+ self.path = path
+ self.python = python
+ return path, python
+
+
+class CreatePackages(fixtures.Fixture):
+ """Creates packages from dict with defaults
+
+ :param package_dirs: A dict of package name to directory strings
+ {'pkg_a': '/tmp/path/to/tmp/pkg_a', 'pkg_b': '/tmp/path/to/tmp/pkg_b'}
+ """
+
+ defaults = {
+ 'setup.py': textwrap.dedent(six.u("""\
+ #!/usr/bin/env python
+ import setuptools
+ setuptools.setup(
+ setup_requires=['pbr'],
+ pbr=True,
+ )
+ """)),
+ 'setup.cfg': textwrap.dedent(six.u("""\
+ [metadata]
+ name = {pkg_name}
+ """))
+ }
+
+ def __init__(self, packages):
+ """Creates packages from dict with defaults
+
+ :param packages: a dict where the keys are the package name and a
+ value that is a second dict that may be empty, containing keys of
+ filenames and a string value of the contents.
+ {'package-a': {'requirements.txt': 'string', 'setup.cfg': 'string'}
+ """
+ self.packages = packages
+
+ def _writeFile(self, directory, file_name, contents):
+ path = os.path.abspath(os.path.join(directory, file_name))
+ path_dir = os.path.dirname(path)
+ if not os.path.exists(path_dir):
+ if path_dir.startswith(directory):
+ os.makedirs(path_dir)
+ else:
+ raise ValueError
+ with open(path, 'wt') as f:
+ f.write(contents)
+
+ def _setUp(self):
+ tmpdir = self.useFixture(fixtures.TempDir()).path
+ package_dirs = {}
+ for pkg_name in self.packages:
+ pkg_path = os.path.join(tmpdir, pkg_name)
+ package_dirs[pkg_name] = pkg_path
+ os.mkdir(pkg_path)
+ for cf in ['setup.py', 'setup.cfg']:
+ if cf in self.packages[pkg_name]:
+ contents = self.packages[pkg_name].pop(cf)
+ else:
+ contents = self.defaults[cf].format(pkg_name=pkg_name)
+ self._writeFile(pkg_path, cf, contents)
+
+ for cf in self.packages[pkg_name]:
+ self._writeFile(pkg_path, cf, self.packages[pkg_name][cf])
+ self.useFixture(TestRepo(pkg_path)).commit()
+ self.addCleanup(delattr, self, 'package_dirs')
+ self.package_dirs = package_dirs
+ return package_dirs
+
+
+class TestPackagingInGitRepoWithCommit(base.BaseTestCase):
+
+ scenarios = [
+ ('preversioned', dict(preversioned=True)),
+ ('postversioned', dict(preversioned=False)),
+ ]
+
+ def setUp(self):
+ super(TestPackagingInGitRepoWithCommit, self).setUp()
+ repo = self.useFixture(TestRepo(self.package_dir))
+ repo.commit()
+
+ def test_authors(self):
+ self.run_setup('sdist', allow_fail=False)
+ # One commit, something should be in the authors list
+ with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f:
+ body = f.read()
+ self.assertNotEqual(body, '')
+
+ def test_changelog(self):
+ self.run_setup('sdist', allow_fail=False)
+ with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
+ body = f.read()
+ # One commit, something should be in the ChangeLog list
+ self.assertNotEqual(body, '')
+
+ def test_manifest_exclude_honoured(self):
+ self.run_setup('sdist', allow_fail=False)
+ with open(os.path.join(
+ self.package_dir,
+ 'pbr_testpackage.egg-info/SOURCES.txt'), 'r') as f:
+ body = f.read()
+ self.assertThat(
+ body, matchers.Not(matchers.Contains('pbr_testpackage/extra.py')))
+ self.assertThat(body, matchers.Contains('pbr_testpackage/__init__.py'))
+
+ def test_install_writes_changelog(self):
+ stdout, _, _ = self.run_setup(
+ 'install', '--root', self.temp_dir + 'installed',
+ allow_fail=False)
+ self.expectThat(stdout, matchers.Contains('Generating ChangeLog'))
+
+
+class TestExtrafileInstallation(base.BaseTestCase):
+ def test_install_glob(self):
+ stdout, _, _ = self.run_setup(
+ 'install', '--root', self.temp_dir + 'installed',
+ allow_fail=False)
+ self.expectThat(
+ stdout, matchers.Contains('copying data_files/a.txt'))
+ self.expectThat(
+ stdout, matchers.Contains('copying data_files/b.txt'))
+
+
+class TestPackagingInGitRepoWithoutCommit(base.BaseTestCase):
+
+ def setUp(self):
+ super(TestPackagingInGitRepoWithoutCommit, self).setUp()
+ self.useFixture(TestRepo(self.package_dir))
+ self.run_setup('sdist', allow_fail=False)
+
+ def test_authors(self):
+ # No commits, no authors in list
+ with open(os.path.join(self.package_dir, 'AUTHORS'), 'r') as f:
+ body = f.read()
+ self.assertEqual('\n', body)
+
+ def test_changelog(self):
+ # No commits, nothing should be in the ChangeLog list
+ with open(os.path.join(self.package_dir, 'ChangeLog'), 'r') as f:
+ body = f.read()
+ self.assertEqual('CHANGES\n=======\n\n', body)
+
+
+class TestPackagingWheels(base.BaseTestCase):
+
+ def setUp(self):
+ super(TestPackagingWheels, self).setUp()
+ self.useFixture(TestRepo(self.package_dir))
+ # Build the wheel
+ self.run_setup('bdist_wheel', allow_fail=False)
+ # Slowly construct the path to the generated whl
+ dist_dir = os.path.join(self.package_dir, 'dist')
+ relative_wheel_filename = os.listdir(dist_dir)[0]
+ absolute_wheel_filename = os.path.join(
+ dist_dir, relative_wheel_filename)
+ wheel_file = wheel.install.WheelFile(absolute_wheel_filename)
+ wheel_name = wheel_file.parsed_filename.group('namever')
+ # Create a directory path to unpack the wheel to
+ self.extracted_wheel_dir = os.path.join(dist_dir, wheel_name)
+ # Extract the wheel contents to the directory we just created
+ wheel_file.zipfile.extractall(self.extracted_wheel_dir)
+ wheel_file.zipfile.close()
+
+ def test_data_directory_has_wsgi_scripts(self):
+ # Build the path to the scripts directory
+ scripts_dir = os.path.join(
+ self.extracted_wheel_dir, 'pbr_testpackage-0.0.data/scripts')
+ self.assertTrue(os.path.exists(scripts_dir))
+ scripts = os.listdir(scripts_dir)
+
+ self.assertIn('pbr_test_wsgi', scripts)
+ self.assertIn('pbr_test_wsgi_with_class', scripts)
+ self.assertNotIn('pbr_test_cmd', scripts)
+ self.assertNotIn('pbr_test_cmd_with_class', scripts)
+
+ def test_generates_c_extensions(self):
+ built_package_dir = os.path.join(
+ self.extracted_wheel_dir, 'pbr_testpackage')
+ static_object_filename = 'testext.so'
+ soabi = get_soabi()
+ if soabi:
+ static_object_filename = 'testext.{0}.so'.format(soabi)
+ static_object_path = os.path.join(
+ built_package_dir, static_object_filename)
+
+ self.assertTrue(os.path.exists(built_package_dir))
+ self.assertTrue(os.path.exists(static_object_path))
+
+
+class TestPackagingHelpers(testtools.TestCase):
+
+ def test_generate_script(self):
+ group = 'console_scripts'
+ entry_point = pkg_resources.EntryPoint(
+ name='test-ep',
+ module_name='pbr.packaging',
+ attrs=('LocalInstallScripts',))
+ header = '#!/usr/bin/env fake-header\n'
+ template = ('%(group)s %(module_name)s %(import_target)s '
+ '%(invoke_target)s')
+
+ generated_script = packaging.generate_script(
+ group, entry_point, header, template)
+
+ expected_script = (
+ '#!/usr/bin/env fake-header\nconsole_scripts pbr.packaging '
+ 'LocalInstallScripts LocalInstallScripts'
+ )
+ self.assertEqual(expected_script, generated_script)
+
+ def test_generate_script_validates_expectations(self):
+ group = 'console_scripts'
+ entry_point = pkg_resources.EntryPoint(
+ name='test-ep',
+ module_name='pbr.packaging')
+ header = '#!/usr/bin/env fake-header\n'
+ template = ('%(group)s %(module_name)s %(import_target)s '
+ '%(invoke_target)s')
+ self.assertRaises(
+ ValueError, packaging.generate_script, group, entry_point, header,
+ template)
+
+ entry_point = pkg_resources.EntryPoint(
+ name='test-ep',
+ module_name='pbr.packaging',
+ attrs=('attr1', 'attr2', 'attr3'))
+ self.assertRaises(
+ ValueError, packaging.generate_script, group, entry_point, header,
+ template)
+
+
+class TestPackagingInPlainDirectory(base.BaseTestCase):
+
+ def setUp(self):
+ super(TestPackagingInPlainDirectory, self).setUp()
+
+ def test_authors(self):
+ self.run_setup('sdist', allow_fail=False)
+ # Not a git repo, no AUTHORS file created
+ filename = os.path.join(self.package_dir, 'AUTHORS')
+ self.assertFalse(os.path.exists(filename))
+
+ def test_changelog(self):
+ self.run_setup('sdist', allow_fail=False)
+ # Not a git repo, no ChangeLog created
+ filename = os.path.join(self.package_dir, 'ChangeLog')
+ self.assertFalse(os.path.exists(filename))
+
+ def test_install_no_ChangeLog(self):
+ stdout, _, _ = self.run_setup(
+ 'install', '--root', self.temp_dir + 'installed',
+ allow_fail=False)
+ self.expectThat(
+ stdout, matchers.Not(matchers.Contains('Generating ChangeLog')))
+
+
+class TestPresenceOfGit(base.BaseTestCase):
+
+ def testGitIsInstalled(self):
+ with mock.patch.object(git,
+ '_run_shell_command') as _command:
+ _command.return_value = 'git version 1.8.4.1'
+ self.assertEqual(True, git._git_is_installed())
+
+ def testGitIsNotInstalled(self):
+ with mock.patch.object(git,
+ '_run_shell_command') as _command:
+ _command.side_effect = OSError
+ self.assertEqual(False, git._git_is_installed())
+
+
+class TestNestedRequirements(base.BaseTestCase):
+
+ def test_nested_requirement(self):
+ tempdir = tempfile.mkdtemp()
+ requirements = os.path.join(tempdir, 'requirements.txt')
+ nested = os.path.join(tempdir, 'nested.txt')
+ with open(requirements, 'w') as f:
+ f.write('-r ' + nested)
+ with open(nested, 'w') as f:
+ f.write('pbr')
+ result = packaging.parse_requirements([requirements])
+ self.assertEqual(['pbr'], result)
+
+
+class TestVersions(base.BaseTestCase):
+
+ scenarios = [
+ ('preversioned', dict(preversioned=True)),
+ ('postversioned', dict(preversioned=False)),
+ ]
+
+ def setUp(self):
+ super(TestVersions, self).setUp()
+ self.repo = self.useFixture(TestRepo(self.package_dir))
+ self.useFixture(GPGKeyFixture())
+ self.useFixture(base.DiveDir(self.package_dir))
+
+ def test_email_parsing_errors_are_handled(self):
+ mocked_open = mock.mock_open()
+ with mock.patch('pbr.packaging.open', mocked_open):
+ with mock.patch('email.message_from_file') as message_from_file:
+ message_from_file.side_effect = [
+ email.errors.MessageError('Test'),
+ {'Name': 'pbr_testpackage'}]
+ version = packaging._get_version_from_pkg_metadata(
+ 'pbr_testpackage')
+
+ self.assertTrue(message_from_file.called)
+ self.assertIsNone(version)
+
+ def test_capitalized_headers(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit('Sem-Ver: api-break')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
+
+ def test_capitalized_headers_partial(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit('Sem-ver: api-break')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
+
+ def test_tagged_version_has_tag_version(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ version = packaging._get_version_from_git('1.2.3')
+ self.assertEqual('1.2.3', version)
+
+ def test_non_canonical_tagged_version_bump(self):
+ self.repo.commit()
+ self.repo.tag('1.4')
+ self.repo.commit('Sem-Ver: api-break')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
+
+ def test_untagged_version_has_dev_version_postversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit()
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
+
+ def test_untagged_pre_release_has_pre_dev_version_postversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3.0a1')
+ self.repo.commit()
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
+
+ def test_untagged_version_minor_bump(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit('sem-ver: deprecation')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.3.0.dev1'))
+
+ def test_untagged_version_major_bump(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit('sem-ver: api-break')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('2.0.0.dev1'))
+
+ def test_untagged_version_has_dev_version_preversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit()
+ version = packaging._get_version_from_git('1.2.5')
+ self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
+
+ def test_untagged_version_after_pre_has_dev_version_preversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3.0a1')
+ self.repo.commit()
+ version = packaging._get_version_from_git('1.2.5')
+ self.assertThat(version, matchers.StartsWith('1.2.5.dev1'))
+
+ def test_untagged_version_after_rc_has_dev_version_preversion(self):
+ self.repo.commit()
+ self.repo.tag('1.2.3.0a1')
+ self.repo.commit()
+ version = packaging._get_version_from_git('1.2.3')
+ self.assertThat(version, matchers.StartsWith('1.2.3.0a2.dev1'))
+
+ def test_preversion_too_low_simple(self):
+ # That is, the target version is either already released or not high
+ # enough for the semver requirements given api breaks etc.
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit()
+ # Note that we can't target 1.2.3 anymore - with 1.2.3 released we
+ # need to be working on 1.2.4.
+ err = self.assertRaises(
+ ValueError, packaging._get_version_from_git, '1.2.3')
+ self.assertThat(err.args[0], matchers.StartsWith('git history'))
+
+ def test_preversion_too_low_semver_headers(self):
+ # That is, the target version is either already released or not high
+ # enough for the semver requirements given api breaks etc.
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit('sem-ver: feature')
+ # Note that we can't target 1.2.4, the feature header means we need
+ # to be working on 1.3.0 or above.
+ err = self.assertRaises(
+ ValueError, packaging._get_version_from_git, '1.2.4')
+ self.assertThat(err.args[0], matchers.StartsWith('git history'))
+
+ def test_get_kwargs_corner_cases(self):
+ # No tags:
+ git_dir = self.repo._basedir + '/.git'
+ get_kwargs = lambda tag: packaging._get_increment_kwargs(git_dir, tag)
+
+ def _check_combinations(tag):
+ self.repo.commit()
+ self.assertEqual(dict(), get_kwargs(tag))
+ self.repo.commit('sem-ver: bugfix')
+ self.assertEqual(dict(), get_kwargs(tag))
+ self.repo.commit('sem-ver: feature')
+ self.assertEqual(dict(minor=True), get_kwargs(tag))
+ self.repo.uncommit()
+ self.repo.commit('sem-ver: deprecation')
+ self.assertEqual(dict(minor=True), get_kwargs(tag))
+ self.repo.uncommit()
+ self.repo.commit('sem-ver: api-break')
+ self.assertEqual(dict(major=True), get_kwargs(tag))
+ self.repo.commit('sem-ver: deprecation')
+ self.assertEqual(dict(major=True, minor=True), get_kwargs(tag))
+ _check_combinations('')
+ self.repo.tag('1.2.3')
+ _check_combinations('1.2.3')
+
+ def test_invalid_tag_ignored(self):
+ # Fix for bug 1356784 - we treated any tag as a version, not just those
+ # that are valid versions.
+ self.repo.commit()
+ self.repo.tag('1')
+ self.repo.commit()
+ # when the tree is tagged and its wrong:
+ self.repo.tag('badver')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.0.1.dev1'))
+ # When the tree isn't tagged, we also fall through.
+ self.repo.commit()
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.0.1.dev2'))
+ # We don't fall through x.y versions
+ self.repo.commit()
+ self.repo.tag('1.2')
+ self.repo.commit()
+ self.repo.tag('badver2')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.2.1.dev1'))
+ # Or x.y.z versions
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ self.repo.commit()
+ self.repo.tag('badver3')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.2.4.dev1'))
+ # Or alpha/beta/pre versions
+ self.repo.commit()
+ self.repo.tag('1.2.4.0a1')
+ self.repo.commit()
+ self.repo.tag('badver4')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('1.2.4.0a2.dev1'))
+ # Non-release related tags are ignored.
+ self.repo.commit()
+ self.repo.tag('2')
+ self.repo.commit()
+ self.repo.tag('non-release-tag/2014.12.16-1')
+ version = packaging._get_version_from_git()
+ self.assertThat(version, matchers.StartsWith('2.0.1.dev1'))
+
+ def test_valid_tag_honoured(self):
+ # Fix for bug 1370608 - we converted any target into a 'dev version'
+ # even if there was a distance of 0 - indicating that we were on the
+ # tag itself.
+ self.repo.commit()
+ self.repo.tag('1.3.0.0a1')
+ version = packaging._get_version_from_git()
+ self.assertEqual('1.3.0.0a1', version)
+
+ def test_skip_write_git_changelog(self):
+ # Fix for bug 1467440
+ self.repo.commit()
+ self.repo.tag('1.2.3')
+ os.environ['SKIP_WRITE_GIT_CHANGELOG'] = '1'
+ version = packaging._get_version_from_git('1.2.3')
+ self.assertEqual('1.2.3', version)
+
+ def tearDown(self):
+ super(TestVersions, self).tearDown()
+ os.environ.pop('SKIP_WRITE_GIT_CHANGELOG', None)
+
+
+class TestRequirementParsing(base.BaseTestCase):
+
+ def test_requirement_parsing(self):
+ pkgs = {
+ 'test_reqparse':
+ {
+ 'requirements.txt': textwrap.dedent("""\
+ bar
+ quux<1.0; python_version=='2.6'
+ requests-aws>=0.1.4 # BSD License (3 clause)
+ Routes>=1.12.3,!=2.0,!=2.1;python_version=='2.7'
+ requests-kerberos>=0.6;python_version=='2.7' # MIT
+ """),
+ 'setup.cfg': textwrap.dedent("""\
+ [metadata]
+ name = test_reqparse
+
+ [extras]
+ test =
+ foo
+ baz>3.2 :python_version=='2.7' # MIT
+ bar>3.3 :python_version=='2.7' # MIT # Apache
+ """)},
+ }
+ pkg_dirs = self.useFixture(CreatePackages(pkgs)).package_dirs
+ pkg_dir = pkg_dirs['test_reqparse']
+ # pkg_resources.split_sections uses None as the title of an
+ # anonymous section instead of the empty string. Weird.
+ expected_requirements = {
+ None: ['bar', 'requests-aws>=0.1.4'],
+ ":(python_version=='2.6')": ['quux<1.0'],
+ ":(python_version=='2.7')": ['Routes>=1.12.3,!=2.0,!=2.1',
+ 'requests-kerberos>=0.6'],
+ 'test': ['foo'],
+ "test:(python_version=='2.7')": ['baz>3.2', 'bar>3.3']
+ }
+ venv = self.useFixture(Venv('reqParse'))
+ bin_python = venv.python
+ # Two things are tested by this
+ # 1) pbr properly parses markers from requiremnts.txt and setup.cfg
+ # 2) bdist_wheel causes pbr to not evaluate markers
+ self._run_cmd(bin_python, ('setup.py', 'bdist_wheel'),
+ allow_fail=False, cwd=pkg_dir)
+ egg_info = os.path.join(pkg_dir, 'test_reqparse.egg-info')
+
+ requires_txt = os.path.join(egg_info, 'requires.txt')
+ with open(requires_txt, 'rt') as requires:
+ generated_requirements = dict(
+ pkg_resources.split_sections(requires))
+
+ self.assertEqual(expected_requirements, generated_requirements)
+
+
+def get_soabi():
+ soabi = None
+ try:
+ soabi = sysconfig.get_config_var('SOABI')
+ arch = sysconfig.get_config_var('MULTIARCH')
+ except IOError:
+ pass
+ if soabi and arch and 'pypy' in sysconfig.get_scheme_names():
+ soabi = '%s-%s' % (soabi, arch)
+ if soabi is None and 'pypy' in sysconfig.get_scheme_names():
+ # NOTE(sigmavirus24): PyPy only added support for the SOABI config var
+ # to sysconfig in 2015. That was well after 2.2.1 was published in the
+ # Ubuntu 14.04 archive.
+ for suffix, _, _ in imp.get_suffixes():
+ if suffix.startswith('.pypy') and suffix.endswith('.so'):
+ soabi = suffix.split('.')[1]
+ break
+ return soabi
--- /dev/null
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import mock
+
+from pbr import pbr_json
+from pbr.tests import base
+
+
+class TestJsonContent(base.BaseTestCase):
+ @mock.patch('pbr.git._run_git_functions', return_value=True)
+ @mock.patch('pbr.git.get_git_short_sha', return_value="123456")
+ @mock.patch('pbr.git.get_is_release', return_value=True)
+ def test_content(self, mock_get_is, mock_get_git, mock_run):
+ cmd = mock.Mock()
+ pbr_json.write_pbr_json(cmd, "basename", "pbr.json")
+ cmd.write_file.assert_called_once_with(
+ 'pbr',
+ 'pbr.json',
+ '{"git_version": "123456", "is_release": true}'
+ )
--- /dev/null
+# Copyright (c) 2011 OpenStack Foundation
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from __future__ import print_function
+
+import os
+import sys
+import tempfile
+import testscenarios
+
+try:
+ import cStringIO as io
+ BytesIO = io.StringIO
+except ImportError:
+ import io
+ BytesIO = io.BytesIO
+
+import fixtures
+
+from pbr import git
+from pbr import options
+from pbr import packaging
+from pbr.tests import base
+
+
+class SkipFileWrites(base.BaseTestCase):
+
+ scenarios = [
+ ('changelog_option_true',
+ dict(option_key='skip_changelog', option_value='True',
+ env_key='SKIP_WRITE_GIT_CHANGELOG', env_value=None,
+ pkg_func=git.write_git_changelog, filename='ChangeLog')),
+ ('changelog_option_false',
+ dict(option_key='skip_changelog', option_value='False',
+ env_key='SKIP_WRITE_GIT_CHANGELOG', env_value=None,
+ pkg_func=git.write_git_changelog, filename='ChangeLog')),
+ ('changelog_env_true',
+ dict(option_key='skip_changelog', option_value='False',
+ env_key='SKIP_WRITE_GIT_CHANGELOG', env_value='True',
+ pkg_func=git.write_git_changelog, filename='ChangeLog')),
+ ('changelog_both_true',
+ dict(option_key='skip_changelog', option_value='True',
+ env_key='SKIP_WRITE_GIT_CHANGELOG', env_value='True',
+ pkg_func=git.write_git_changelog, filename='ChangeLog')),
+ ('authors_option_true',
+ dict(option_key='skip_authors', option_value='True',
+ env_key='SKIP_GENERATE_AUTHORS', env_value=None,
+ pkg_func=git.generate_authors, filename='AUTHORS')),
+ ('authors_option_false',
+ dict(option_key='skip_authors', option_value='False',
+ env_key='SKIP_GENERATE_AUTHORS', env_value=None,
+ pkg_func=git.generate_authors, filename='AUTHORS')),
+ ('authors_env_true',
+ dict(option_key='skip_authors', option_value='False',
+ env_key='SKIP_GENERATE_AUTHORS', env_value='True',
+ pkg_func=git.generate_authors, filename='AUTHORS')),
+ ('authors_both_true',
+ dict(option_key='skip_authors', option_value='True',
+ env_key='SKIP_GENERATE_AUTHORS', env_value='True',
+ pkg_func=git.generate_authors, filename='AUTHORS')),
+ ]
+
+ def setUp(self):
+ super(SkipFileWrites, self).setUp()
+ self.temp_path = self.useFixture(fixtures.TempDir()).path
+ self.root_dir = os.path.abspath(os.path.curdir)
+ self.git_dir = os.path.join(self.root_dir, ".git")
+ if not os.path.exists(self.git_dir):
+ self.skipTest("%s is missing; skipping git-related checks"
+ % self.git_dir)
+ return
+ self.filename = os.path.join(self.temp_path, self.filename)
+ self.option_dict = dict()
+ if self.option_key is not None:
+ self.option_dict[self.option_key] = ('setup.cfg',
+ self.option_value)
+ self.useFixture(
+ fixtures.EnvironmentVariable(self.env_key, self.env_value))
+
+ def test_skip(self):
+ self.pkg_func(git_dir=self.git_dir,
+ dest_dir=self.temp_path,
+ option_dict=self.option_dict)
+ self.assertEqual(
+ not os.path.exists(self.filename),
+ (self.option_value.lower() in options.TRUE_VALUES
+ or self.env_value is not None))
+
+_changelog_content = """7780758\x00Break parser\x00 (tag: refs/tags/1_foo.1)
+04316fe\x00Make python\x00 (refs/heads/review/monty_taylor/27519)
+378261a\x00Add an integration test script.\x00
+3c373ac\x00Merge "Lib\x00 (HEAD, tag: refs/tags/2013.2.rc2, tag: refs/tags/2013.2, refs/heads/mile-proposed)
+182feb3\x00Fix pip invocation for old versions of pip.\x00 (tag: refs/tags/0.5.17)
+fa4f46e\x00Remove explicit depend on distribute.\x00 (tag: refs/tags/0.5.16)
+d1c53dd\x00Use pip instead of easy_install for installation.\x00
+a793ea1\x00Merge "Skip git-checkout related tests when .git is missing"\x00
+6c27ce7\x00Skip git-checkout related tests when .git is missing\x00
+451e513\x00Bug fix: create_stack() fails when waiting\x00
+4c8cfe4\x00Improve test coverage: network delete API\x00 (tag: refs/tags/(evil))
+d7e6167\x00Bug fix: Fix pass thru filtering in list_networks\x00 (tag: refs/tags/ev()il)
+c47ec15\x00Consider 'in-use' a non-pending volume for caching\x00 (tag: refs/tags/ev)il)
+8696fbd\x00Improve test coverage: private extension API\x00 (tag: refs/tags/ev(il)
+f0440f8\x00Improve test coverage: hypervisor list\x00 (tag: refs/tags/e(vi)l)
+04984a5\x00Refactor hooks file.\x00 (HEAD, tag: 0.6.7,b, tag: refs/tags/(12), refs/heads/master)
+a65e8ee\x00Remove jinja pin.\x00 (tag: refs/tags/0.5.14, tag: refs/tags/0.5.13)
+""" # noqa
+
+
+def _make_old_git_changelog_format(line):
+ """Convert post-1.8.1 git log format to pre-1.8.1 git log format"""
+
+ if not line.strip():
+ return line
+ sha, msg, refname = line.split('\x00')
+ refname = refname.replace('tag: ', '')
+ return '\x00'.join((sha, msg, refname))
+
+_old_git_changelog_content = '\n'.join(
+ _make_old_git_changelog_format(line)
+ for line in _changelog_content.split('\n'))
+
+
+class GitLogsTest(base.BaseTestCase):
+
+ scenarios = [
+ ('pre1.8.3', {'changelog': _old_git_changelog_content}),
+ ('post1.8.3', {'changelog': _changelog_content}),
+ ]
+
+ def setUp(self):
+ super(GitLogsTest, self).setUp()
+ self.temp_path = self.useFixture(fixtures.TempDir()).path
+ self.root_dir = os.path.abspath(os.path.curdir)
+ self.git_dir = os.path.join(self.root_dir, ".git")
+ self.useFixture(
+ fixtures.EnvironmentVariable('SKIP_GENERATE_AUTHORS'))
+ self.useFixture(
+ fixtures.EnvironmentVariable('SKIP_WRITE_GIT_CHANGELOG'))
+
+ def test_write_git_changelog(self):
+ self.useFixture(fixtures.FakePopen(lambda _: {
+ "stdout": BytesIO(self.changelog.encode('utf-8'))
+ }))
+
+ git.write_git_changelog(git_dir=self.git_dir,
+ dest_dir=self.temp_path)
+
+ with open(os.path.join(self.temp_path, "ChangeLog"), "r") as ch_fh:
+ changelog_contents = ch_fh.read()
+ self.assertIn("2013.2", changelog_contents)
+ self.assertIn("0.5.17", changelog_contents)
+ self.assertIn("------", changelog_contents)
+ self.assertIn("Refactor hooks file", changelog_contents)
+ self.assertIn(
+ "Bug fix: create_stack() fails when waiting",
+ changelog_contents)
+ self.assertNotIn("Refactor hooks file.", changelog_contents)
+ self.assertNotIn("182feb3", changelog_contents)
+ self.assertNotIn("review/monty_taylor/27519", changelog_contents)
+ self.assertNotIn("0.5.13", changelog_contents)
+ self.assertNotIn("0.6.7", changelog_contents)
+ self.assertNotIn("12", changelog_contents)
+ self.assertNotIn("(evil)", changelog_contents)
+ self.assertNotIn("ev()il", changelog_contents)
+ self.assertNotIn("ev(il", changelog_contents)
+ self.assertNotIn("ev)il", changelog_contents)
+ self.assertNotIn("e(vi)l", changelog_contents)
+ self.assertNotIn('Merge "', changelog_contents)
+ self.assertNotIn('1_foo.1', changelog_contents)
+
+ def test_generate_authors(self):
+ author_old = u"Foo Foo <email@foo.com>"
+ author_new = u"Bar Bar <email@bar.com>"
+ co_author = u"Foo Bar <foo@bar.com>"
+ co_author_by = u"Co-authored-by: " + co_author
+
+ git_log_cmd = (
+ "git --git-dir=%s log --format=%%aN <%%aE>"
+ % self.git_dir)
+ git_co_log_cmd = ("git --git-dir=%s log" % self.git_dir)
+ git_top_level = "git rev-parse --show-toplevel"
+ cmd_map = {
+ git_log_cmd: author_new,
+ git_co_log_cmd: co_author_by,
+ git_top_level: self.root_dir,
+ }
+
+ exist_files = [self.git_dir,
+ os.path.join(self.temp_path, "AUTHORS.in")]
+ self.useFixture(fixtures.MonkeyPatch(
+ "os.path.exists",
+ lambda path: os.path.abspath(path) in exist_files))
+
+ def _fake_run_shell_command(cmd, **kwargs):
+ return cmd_map[" ".join(cmd)]
+
+ self.useFixture(fixtures.MonkeyPatch(
+ "pbr.git._run_shell_command",
+ _fake_run_shell_command))
+
+ with open(os.path.join(self.temp_path, "AUTHORS.in"), "w") as auth_fh:
+ auth_fh.write("%s\n" % author_old)
+
+ git.generate_authors(git_dir=self.git_dir,
+ dest_dir=self.temp_path)
+
+ with open(os.path.join(self.temp_path, "AUTHORS"), "r") as auth_fh:
+ authors = auth_fh.read()
+ self.assertTrue(author_old in authors)
+ self.assertTrue(author_new in authors)
+ self.assertTrue(co_author in authors)
+
+
+class BaseSphinxTest(base.BaseTestCase):
+
+ def setUp(self):
+ super(BaseSphinxTest, self).setUp()
+
+ self.useFixture(fixtures.MonkeyPatch(
+ "sphinx.application.Sphinx.__init__", lambda *a, **kw: None))
+ self.useFixture(fixtures.MonkeyPatch(
+ "sphinx.application.Sphinx.build", lambda *a, **kw: None))
+ self.useFixture(fixtures.MonkeyPatch(
+ "sphinx.config.Config.man_pages", ['foo']))
+ self.useFixture(fixtures.MonkeyPatch(
+ "sphinx.config.Config.init_values", lambda *a: None))
+ self.useFixture(fixtures.MonkeyPatch(
+ "sphinx.config.Config.__init__", lambda *a: None))
+ from distutils import dist
+ self.distr = dist.Distribution()
+ self.distr.packages = ("fake_package",)
+ self.distr.command_options["build_sphinx"] = {
+ "source_dir": ["a", "."]}
+ pkg_fixture = fixtures.PythonPackage(
+ "fake_package", [("fake_module.py", b""),
+ ("another_fake_module_for_testing.py", b""),
+ ("fake_private_module.py", b"")])
+ self.useFixture(pkg_fixture)
+ self.useFixture(base.DiveDir(pkg_fixture.base))
+ self.distr.command_options["pbr"] = {}
+ if hasattr(self, "excludes"):
+ self.distr.command_options["pbr"]["autodoc_exclude_modules"] = (
+ 'setup.cfg',
+ "fake_package.fake_private_module\n"
+ "fake_package.another_fake_*\n"
+ "fake_package.unknown_module")
+ if hasattr(self, 'has_opt') and self.has_opt:
+ options = self.distr.command_options["pbr"]
+ options["autodoc_index_modules"] = ('setup.cfg', self.autodoc)
+
+
+class BuildSphinxTest(BaseSphinxTest):
+
+ scenarios = [
+ ('true_autodoc_caps',
+ dict(has_opt=True, autodoc='True', has_autodoc=True)),
+ ('true_autodoc_caps_with_excludes',
+ dict(has_opt=True, autodoc='True', has_autodoc=True,
+ excludes="fake_package.fake_private_module\n"
+ "fake_package.another_fake_*\n"
+ "fake_package.unknown_module")),
+ ('true_autodoc_lower',
+ dict(has_opt=True, autodoc='true', has_autodoc=True)),
+ ('false_autodoc',
+ dict(has_opt=True, autodoc='False', has_autodoc=False)),
+ ('no_autodoc',
+ dict(has_opt=False, autodoc='False', has_autodoc=False)),
+ ]
+
+ def test_build_doc(self):
+ build_doc = packaging.LocalBuildDoc(self.distr)
+ build_doc.run()
+
+ self.assertTrue(
+ os.path.exists("api/autoindex.rst") == self.has_autodoc)
+ self.assertTrue(
+ os.path.exists(
+ "api/fake_package.fake_module.rst") == self.has_autodoc)
+ if not self.has_autodoc or hasattr(self, "excludes"):
+ assertion = self.assertFalse
+ else:
+ assertion = self.assertTrue
+ assertion(
+ os.path.exists(
+ "api/fake_package.fake_private_module.rst"))
+ assertion(
+ os.path.exists(
+ "api/fake_package.another_fake_module_for_testing.rst"))
+
+ def test_builders_config(self):
+ build_doc = packaging.LocalBuildDoc(self.distr)
+ build_doc.finalize_options()
+
+ self.assertEqual(2, len(build_doc.builders))
+ self.assertIn('html', build_doc.builders)
+ self.assertIn('man', build_doc.builders)
+
+ build_doc = packaging.LocalBuildDoc(self.distr)
+ build_doc.builders = ''
+ build_doc.finalize_options()
+
+ self.assertEqual('', build_doc.builders)
+
+ build_doc = packaging.LocalBuildDoc(self.distr)
+ build_doc.builders = 'man'
+ build_doc.finalize_options()
+
+ self.assertEqual(1, len(build_doc.builders))
+ self.assertIn('man', build_doc.builders)
+
+ build_doc = packaging.LocalBuildDoc(self.distr)
+ build_doc.builders = 'html,man,doctest'
+ build_doc.finalize_options()
+
+ self.assertIn('html', build_doc.builders)
+ self.assertIn('man', build_doc.builders)
+ self.assertIn('doctest', build_doc.builders)
+
+ def test_cmd_builder_override(self):
+
+ if self.has_opt:
+ self.distr.command_options["pbr"] = {
+ "autodoc_index_modules": ('setup.cfg', self.autodoc)
+ }
+
+ self.distr.command_options["build_sphinx"]["builder"] = (
+ "command line", "non-existing-builder")
+
+ build_doc = packaging.LocalBuildDoc(self.distr)
+ self.assertNotIn('non-existing-builder', build_doc.builders)
+ self.assertIn('html', build_doc.builders)
+
+ # process command line options which should override config
+ build_doc.finalize_options()
+
+ self.assertIn('non-existing-builder', build_doc.builders)
+ self.assertNotIn('html', build_doc.builders)
+
+ def test_cmd_builder_override_multiple_builders(self):
+
+ if self.has_opt:
+ self.distr.command_options["pbr"] = {
+ "autodoc_index_modules": ('setup.cfg', self.autodoc)
+ }
+
+ self.distr.command_options["build_sphinx"]["builder"] = (
+ "command line", "builder1,builder2")
+
+ build_doc = packaging.LocalBuildDoc(self.distr)
+ build_doc.finalize_options()
+
+ self.assertEqual(["builder1", "builder2"], build_doc.builders)
+
+
+class ParseRequirementsTestScenarios(base.BaseTestCase):
+
+ versioned_scenarios = [
+ ('non-versioned', {'versioned': False, 'expected': ['bar']}),
+ ('versioned', {'versioned': True, 'expected': ['bar>=1.2.3']})
+ ]
+
+ scenarios = [
+ ('normal', {'url': "foo\nbar", 'expected': ['foo', 'bar']}),
+ ('normal_with_comments', {
+ 'url': "# this is a comment\nfoo\n# and another one\nbar",
+ 'expected': ['foo', 'bar']}),
+ ('removes_index_lines', {'url': '-f foobar', 'expected': []}),
+ ]
+
+ scenarios = scenarios + testscenarios.multiply_scenarios([
+ ('ssh_egg_url', {'url': 'git+ssh://foo.com/zipball#egg=bar'}),
+ ('git_https_egg_url', {'url': 'git+https://foo.com/zipball#egg=bar'}),
+ ('http_egg_url', {'url': 'https://foo.com/zipball#egg=bar'}),
+ ], versioned_scenarios)
+
+ scenarios = scenarios + testscenarios.multiply_scenarios(
+ [
+ ('git_egg_url',
+ {'url': 'git://foo.com/zipball#egg=bar', 'name': 'bar'})
+ ], [
+ ('non-editable', {'editable': False}),
+ ('editable', {'editable': True}),
+ ],
+ versioned_scenarios)
+
+ def test_parse_requirements(self):
+ tmp_file = tempfile.NamedTemporaryFile()
+ req_string = self.url
+ if hasattr(self, 'editable') and self.editable:
+ req_string = ("-e %s" % req_string)
+ if hasattr(self, 'versioned') and self.versioned:
+ req_string = ("%s-1.2.3" % req_string)
+ with open(tmp_file.name, 'w') as fh:
+ fh.write(req_string)
+ self.assertEqual(self.expected,
+ packaging.parse_requirements([tmp_file.name]))
+
+
+class ParseRequirementsTest(base.BaseTestCase):
+
+ def setUp(self):
+ super(ParseRequirementsTest, self).setUp()
+ (fd, self.tmp_file) = tempfile.mkstemp(prefix='openstack',
+ suffix='.setup')
+
+ def test_parse_requirements_override_with_env(self):
+ with open(self.tmp_file, 'w') as fh:
+ fh.write("foo\nbar")
+ self.useFixture(
+ fixtures.EnvironmentVariable('PBR_REQUIREMENTS_FILES',
+ self.tmp_file))
+ self.assertEqual(['foo', 'bar'],
+ packaging.parse_requirements())
+
+ def test_parse_requirements_override_with_env_multiple_files(self):
+ with open(self.tmp_file, 'w') as fh:
+ fh.write("foo\nbar")
+ self.useFixture(
+ fixtures.EnvironmentVariable('PBR_REQUIREMENTS_FILES',
+ "no-such-file," + self.tmp_file))
+ self.assertEqual(['foo', 'bar'],
+ packaging.parse_requirements())
+
+ def test_get_requirement_from_file_empty(self):
+ actual = packaging.get_reqs_from_files([])
+ self.assertEqual([], actual)
+
+ def test_parse_requirements_python_version(self):
+ with open("requirements-py%d.txt" % sys.version_info[0],
+ "w") as fh:
+ fh.write("# this is a comment\nfoobar\n# and another one\nfoobaz")
+ self.assertEqual(['foobar', 'foobaz'],
+ packaging.parse_requirements())
+
+ def test_parse_requirements_right_python_version(self):
+ with open("requirements-py1.txt", "w") as fh:
+ fh.write("thisisatrap")
+ with open("requirements-py%d.txt" % sys.version_info[0],
+ "w") as fh:
+ fh.write("# this is a comment\nfoobar\n# and another one\nfoobaz")
+ self.assertEqual(['foobar', 'foobaz'],
+ packaging.parse_requirements())
+
+
+class ParseDependencyLinksTest(base.BaseTestCase):
+
+ def setUp(self):
+ super(ParseDependencyLinksTest, self).setUp()
+ (fd, self.tmp_file) = tempfile.mkstemp(prefix="openstack",
+ suffix=".setup")
+
+ def test_parse_dependency_normal(self):
+ with open(self.tmp_file, "w") as fh:
+ fh.write("http://test.com\n")
+ self.assertEqual(
+ ["http://test.com"],
+ packaging.parse_dependency_links([self.tmp_file]))
+
+ def test_parse_dependency_with_git_egg_url(self):
+ with open(self.tmp_file, "w") as fh:
+ fh.write("-e git://foo.com/zipball#egg=bar")
+ self.assertEqual(
+ ["git://foo.com/zipball#egg=bar"],
+ packaging.parse_dependency_links([self.tmp_file]))
--- /dev/null
+# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. (HP)
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import io
+import textwrap
+
+import six
+from six.moves import configparser
+import sys
+
+from pbr.tests import base
+from pbr import util
+
+
+class TestExtrasRequireParsingScenarios(base.BaseTestCase):
+
+ scenarios = [
+ ('simple_extras', {
+ 'config_text': """
+ [extras]
+ first =
+ foo
+ bar==1.0
+ second =
+ baz>=3.2
+ foo
+ """,
+ 'expected_extra_requires': {'first': ['foo', 'bar==1.0'],
+ 'second': ['baz>=3.2', 'foo']}
+ }),
+ ('with_markers', {
+ 'config_text': """
+ [extras]
+ test =
+ foo:python_version=='2.6'
+ bar
+ baz<1.6 :python_version=='2.6'
+ zaz :python_version>'1.0'
+ """,
+ 'expected_extra_requires': {
+ "test:(python_version=='2.6')": ['foo', 'baz<1.6'],
+ "test": ['bar', 'zaz']}}),
+ ('no_extras', {
+ 'config_text': """
+ [metadata]
+ long_description = foo
+ """,
+ 'expected_extra_requires':
+ {}
+ })]
+
+ def config_from_ini(self, ini):
+ config = {}
+ if sys.version_info >= (3, 2):
+ parser = configparser.ConfigParser()
+ else:
+ parser = configparser.SafeConfigParser()
+ ini = textwrap.dedent(six.u(ini))
+ parser.readfp(io.StringIO(ini))
+ for section in parser.sections():
+ config[section] = dict(parser.items(section))
+ return config
+
+ def test_extras_parsing(self):
+ config = self.config_from_ini(self.config_text)
+ kwargs = util.setup_cfg_to_setup_kwargs(config)
+
+ self.assertEqual(self.expected_extra_requires,
+ kwargs['extras_require'])
+
+
+class TestInvalidMarkers(base.BaseTestCase):
+
+ def test_invalid_marker_raises_error(self):
+ config = {'extras': {'test': "foo :bad_marker>'1.0'"}}
+ self.assertRaises(SyntaxError, util.setup_cfg_to_setup_kwargs, config)
--- /dev/null
+# Copyright 2012 Red Hat, Inc.
+# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import itertools
+
+from testtools import matchers
+
+from pbr.tests import base
+from pbr import version
+
+
+from_pip_string = version.SemanticVersion.from_pip_string
+
+
+class TestSemanticVersion(base.BaseTestCase):
+
+ def test_ordering(self):
+ ordered_versions = [
+ "1.2.3.dev6",
+ "1.2.3.dev7",
+ "1.2.3.a4.dev12",
+ "1.2.3.a4.dev13",
+ "1.2.3.a4",
+ "1.2.3.a5.dev1",
+ "1.2.3.a5",
+ "1.2.3.b3.dev1",
+ "1.2.3.b3",
+ "1.2.3.rc2.dev1",
+ "1.2.3.rc2",
+ "1.2.3.rc3.dev1",
+ "1.2.3",
+ "1.2.4",
+ "1.3.3",
+ "2.2.3",
+ ]
+ for v in ordered_versions:
+ sv = version.SemanticVersion.from_pip_string(v)
+ self.expectThat(sv, matchers.Equals(sv))
+ for left, right in itertools.combinations(ordered_versions, 2):
+ l_pos = ordered_versions.index(left)
+ r_pos = ordered_versions.index(right)
+ if l_pos < r_pos:
+ m1 = matchers.LessThan
+ m2 = matchers.GreaterThan
+ else:
+ m1 = matchers.GreaterThan
+ m2 = matchers.LessThan
+ left_sv = version.SemanticVersion.from_pip_string(left)
+ right_sv = version.SemanticVersion.from_pip_string(right)
+ self.expectThat(left_sv, m1(right_sv))
+ self.expectThat(right_sv, m2(left_sv))
+
+ def test_from_pip_string_legacy_alpha(self):
+ expected = version.SemanticVersion(
+ 1, 2, 0, prerelease_type='rc', prerelease=1)
+ parsed = from_pip_string('1.2.0rc1')
+ self.assertEqual(expected, parsed)
+
+ def test_from_pip_string_legacy_postN(self):
+ # When pbr trunk was incompatible with PEP-440, a stable release was
+ # made that used postN versions to represent developer builds. As
+ # we expect only to be parsing versions of our own, we map those
+ # into dev builds of the next version.
+ expected = version.SemanticVersion(1, 2, 4, dev_count=5)
+ parsed = from_pip_string('1.2.3.post5')
+ self.expectThat(expected, matchers.Equals(parsed))
+ expected = version.SemanticVersion(1, 2, 3, 'a', 5, dev_count=6)
+ parsed = from_pip_string('1.2.3.0a4.post6')
+ self.expectThat(expected, matchers.Equals(parsed))
+ # We can't define a mapping for .postN.devM, so it should raise.
+ self.expectThat(
+ lambda: from_pip_string('1.2.3.post5.dev6'),
+ matchers.raises(ValueError))
+
+ def test_from_pip_string_legacy_nonzero_lead_in(self):
+ # reported in bug 1361251
+ expected = version.SemanticVersion(
+ 0, 0, 1, prerelease_type='a', prerelease=2)
+ parsed = from_pip_string('0.0.1a2')
+ self.assertEqual(expected, parsed)
+
+ def test_from_pip_string_legacy_short_nonzero_lead_in(self):
+ expected = version.SemanticVersion(
+ 0, 1, 0, prerelease_type='a', prerelease=2)
+ parsed = from_pip_string('0.1a2')
+ self.assertEqual(expected, parsed)
+
+ def test_from_pip_string_legacy_no_0_prerelease(self):
+ expected = version.SemanticVersion(
+ 2, 1, 0, prerelease_type='rc', prerelease=1)
+ parsed = from_pip_string('2.1.0.rc1')
+ self.assertEqual(expected, parsed)
+
+ def test_from_pip_string_legacy_no_0_prerelease_2(self):
+ expected = version.SemanticVersion(
+ 2, 0, 0, prerelease_type='rc', prerelease=1)
+ parsed = from_pip_string('2.0.0.rc1')
+ self.assertEqual(expected, parsed)
+
+ def test_from_pip_string_legacy_non_440_beta(self):
+ expected = version.SemanticVersion(
+ 2014, 2, prerelease_type='b', prerelease=2)
+ parsed = from_pip_string('2014.2.b2')
+ self.assertEqual(expected, parsed)
+
+ def test_from_pip_string_pure_git_hash(self):
+ self.assertRaises(ValueError, from_pip_string, '6eed5ae')
+
+ def test_from_pip_string_non_digit_start(self):
+ self.assertRaises(ValueError, from_pip_string,
+ 'non-release-tag/2014.12.16-1')
+
+ def test_final_version(self):
+ semver = version.SemanticVersion(1, 2, 3)
+ self.assertEqual((1, 2, 3, 'final', 0), semver.version_tuple())
+ self.assertEqual("1.2.3", semver.brief_string())
+ self.assertEqual("1.2.3", semver.debian_string())
+ self.assertEqual("1.2.3", semver.release_string())
+ self.assertEqual("1.2.3", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.3"))
+
+ def test_parsing_short_forms(self):
+ semver = version.SemanticVersion(1, 0, 0)
+ self.assertEqual(semver, from_pip_string("1"))
+ self.assertEqual(semver, from_pip_string("1.0"))
+ self.assertEqual(semver, from_pip_string("1.0.0"))
+
+ def test_dev_version(self):
+ semver = version.SemanticVersion(1, 2, 4, dev_count=5)
+ self.assertEqual((1, 2, 4, 'dev', 4), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~dev5", semver.debian_string())
+ self.assertEqual("1.2.4.dev5", semver.release_string())
+ self.assertEqual("1.2.3.dev5", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.dev5"))
+
+ def test_dev_no_git_version(self):
+ semver = version.SemanticVersion(1, 2, 4, dev_count=5)
+ self.assertEqual((1, 2, 4, 'dev', 4), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~dev5", semver.debian_string())
+ self.assertEqual("1.2.4.dev5", semver.release_string())
+ self.assertEqual("1.2.3.dev5", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.dev5"))
+
+ def test_dev_zero_version(self):
+ semver = version.SemanticVersion(1, 2, 0, dev_count=5)
+ self.assertEqual((1, 2, 0, 'dev', 4), semver.version_tuple())
+ self.assertEqual("1.2.0", semver.brief_string())
+ self.assertEqual("1.2.0~dev5", semver.debian_string())
+ self.assertEqual("1.2.0.dev5", semver.release_string())
+ self.assertEqual("1.1.9999.dev5", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.0.dev5"))
+
+ def test_alpha_dev_version(self):
+ semver = version.SemanticVersion(1, 2, 4, 'a', 1, 12)
+ self.assertEqual((1, 2, 4, 'alphadev', 12), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~a1.dev12", semver.debian_string())
+ self.assertEqual("1.2.4.0a1.dev12", semver.release_string())
+ self.assertEqual("1.2.3.a1.dev12", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0a1.dev12"))
+
+ def test_alpha_version(self):
+ semver = version.SemanticVersion(1, 2, 4, 'a', 1)
+ self.assertEqual((1, 2, 4, 'alpha', 1), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~a1", semver.debian_string())
+ self.assertEqual("1.2.4.0a1", semver.release_string())
+ self.assertEqual("1.2.3.a1", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0a1"))
+
+ def test_alpha_zero_version(self):
+ semver = version.SemanticVersion(1, 2, 0, 'a', 1)
+ self.assertEqual((1, 2, 0, 'alpha', 1), semver.version_tuple())
+ self.assertEqual("1.2.0", semver.brief_string())
+ self.assertEqual("1.2.0~a1", semver.debian_string())
+ self.assertEqual("1.2.0.0a1", semver.release_string())
+ self.assertEqual("1.1.9999.a1", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.0.0a1"))
+
+ def test_alpha_major_zero_version(self):
+ semver = version.SemanticVersion(1, 0, 0, 'a', 1)
+ self.assertEqual((1, 0, 0, 'alpha', 1), semver.version_tuple())
+ self.assertEqual("1.0.0", semver.brief_string())
+ self.assertEqual("1.0.0~a1", semver.debian_string())
+ self.assertEqual("1.0.0.0a1", semver.release_string())
+ self.assertEqual("0.9999.9999.a1", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.0.0.0a1"))
+
+ def test_alpha_default_version(self):
+ semver = version.SemanticVersion(1, 2, 4, 'a')
+ self.assertEqual((1, 2, 4, 'alpha', 0), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~a0", semver.debian_string())
+ self.assertEqual("1.2.4.0a0", semver.release_string())
+ self.assertEqual("1.2.3.a0", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0a0"))
+
+ def test_beta_dev_version(self):
+ semver = version.SemanticVersion(1, 2, 4, 'b', 1, 12)
+ self.assertEqual((1, 2, 4, 'betadev', 12), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~b1.dev12", semver.debian_string())
+ self.assertEqual("1.2.4.0b1.dev12", semver.release_string())
+ self.assertEqual("1.2.3.b1.dev12", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0b1.dev12"))
+
+ def test_beta_version(self):
+ semver = version.SemanticVersion(1, 2, 4, 'b', 1)
+ self.assertEqual((1, 2, 4, 'beta', 1), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~b1", semver.debian_string())
+ self.assertEqual("1.2.4.0b1", semver.release_string())
+ self.assertEqual("1.2.3.b1", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0b1"))
+
+ def test_decrement_nonrelease(self):
+ # The prior version of any non-release is a release
+ semver = version.SemanticVersion(1, 2, 4, 'b', 1)
+ self.assertEqual(
+ version.SemanticVersion(1, 2, 3), semver.decrement())
+
+ def test_decrement_nonrelease_zero(self):
+ # We set an arbitrary max version of 9999 when decrementing versions
+ # - this is part of handling rpm support.
+ semver = version.SemanticVersion(1, 0, 0)
+ self.assertEqual(
+ version.SemanticVersion(0, 9999, 9999), semver.decrement())
+
+ def test_decrement_release(self):
+ # The next patch version of a release version requires a change to the
+ # patch level.
+ semver = version.SemanticVersion(2, 2, 5)
+ self.assertEqual(
+ version.SemanticVersion(2, 2, 4), semver.decrement())
+
+ def test_increment_nonrelease(self):
+ # The next patch version of a non-release version is another
+ # non-release version as the next release doesn't need to be
+ # incremented.
+ semver = version.SemanticVersion(1, 2, 4, 'b', 1)
+ self.assertEqual(
+ version.SemanticVersion(1, 2, 4, 'b', 2), semver.increment())
+ # Major and minor increments however need to bump things.
+ self.assertEqual(
+ version.SemanticVersion(1, 3, 0), semver.increment(minor=True))
+ self.assertEqual(
+ version.SemanticVersion(2, 0, 0), semver.increment(major=True))
+
+ def test_increment_release(self):
+ # The next patch version of a release version requires a change to the
+ # patch level.
+ semver = version.SemanticVersion(1, 2, 5)
+ self.assertEqual(
+ version.SemanticVersion(1, 2, 6), semver.increment())
+ self.assertEqual(
+ version.SemanticVersion(1, 3, 0), semver.increment(minor=True))
+ self.assertEqual(
+ version.SemanticVersion(2, 0, 0), semver.increment(major=True))
+
+ def test_rc_dev_version(self):
+ semver = version.SemanticVersion(1, 2, 4, 'rc', 1, 12)
+ self.assertEqual((1, 2, 4, 'candidatedev', 12), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~rc1.dev12", semver.debian_string())
+ self.assertEqual("1.2.4.0rc1.dev12", semver.release_string())
+ self.assertEqual("1.2.3.rc1.dev12", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0rc1.dev12"))
+
+ def test_rc_version(self):
+ semver = version.SemanticVersion(1, 2, 4, 'rc', 1)
+ self.assertEqual((1, 2, 4, 'candidate', 1), semver.version_tuple())
+ self.assertEqual("1.2.4", semver.brief_string())
+ self.assertEqual("1.2.4~rc1", semver.debian_string())
+ self.assertEqual("1.2.4.0rc1", semver.release_string())
+ self.assertEqual("1.2.3.rc1", semver.rpm_string())
+ self.assertEqual(semver, from_pip_string("1.2.4.0rc1"))
+
+ def test_to_dev(self):
+ self.assertEqual(
+ version.SemanticVersion(1, 2, 3, dev_count=1),
+ version.SemanticVersion(1, 2, 3).to_dev(1))
+ self.assertEqual(
+ version.SemanticVersion(1, 2, 3, 'rc', 1, dev_count=1),
+ version.SemanticVersion(1, 2, 3, 'rc', 1).to_dev(1))
--- /dev/null
+# Copyright (c) 2015 Hewlett-Packard Development Company, L.P. (HP)
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import os
+import re
+import subprocess
+import sys
+try:
+ # python 2
+ from urllib2 import urlopen
+except ImportError:
+ # python 3
+ from urllib.request import urlopen
+
+from pbr.tests import base
+
+
+class TestWsgiScripts(base.BaseTestCase):
+
+ cmd_names = ('pbr_test_wsgi', 'pbr_test_wsgi_with_class')
+
+ def _get_path(self):
+ if os.path.isdir("%s/lib64" % self.temp_dir):
+ path = "%s/lib64" % self.temp_dir
+ elif os.path.isdir("%s/lib" % self.temp_dir):
+ path = "%s/lib" % self.temp_dir
+ elif os.path.isdir("%s/site-packages" % self.temp_dir):
+ return ".:%s/site-packages" % self.temp_dir
+ else:
+ raise Exception("Could not determine path for test")
+ return ".:%s/python%s.%s/site-packages" % (
+ path,
+ sys.version_info[0],
+ sys.version_info[1])
+
+ def test_wsgi_script_install(self):
+ """Test that we install a non-pkg-resources wsgi script."""
+ if os.name == 'nt':
+ self.skipTest('Windows support is passthrough')
+
+ stdout, _, return_code = self.run_setup(
+ 'install', '--prefix=%s' % self.temp_dir)
+
+ self._check_wsgi_install_content(stdout)
+
+ def test_wsgi_script_run(self):
+ """Test that we install a runnable wsgi script.
+
+ This test actually attempts to start and interact with the
+ wsgi script in question to demonstrate that it's a working
+ wsgi script using simple server.
+
+ """
+ if os.name == 'nt':
+ self.skipTest('Windows support is passthrough')
+
+ stdout, _, return_code = self.run_setup(
+ 'install', '--prefix=%s' % self.temp_dir)
+
+ self._check_wsgi_install_content(stdout)
+
+ # Live test run the scripts and see that they respond to wsgi
+ # requests.
+ for cmd_name in self.cmd_names:
+ self._test_wsgi(cmd_name, b'Hello World')
+
+ def _test_wsgi(self, cmd_name, output, extra_args=None):
+ cmd = os.path.join(self.temp_dir, 'bin', cmd_name)
+ print("Running %s -p 0" % cmd)
+ popen_cmd = [cmd, '-p', '0']
+ if extra_args:
+ popen_cmd.extend(extra_args)
+
+ env = {'PYTHONPATH': self._get_path()}
+
+ p = subprocess.Popen(popen_cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, cwd=self.temp_dir,
+ env=env)
+ self.addCleanup(p.kill)
+
+ stdoutdata = p.stdout.readline() # ****...
+
+ stdoutdata = p.stdout.readline() # STARTING test server...
+ self.assertIn(
+ b"STARTING test server pbr_testpackage.wsgi",
+ stdoutdata)
+
+ stdoutdata = p.stdout.readline() # Available at ...
+ print(stdoutdata)
+ m = re.search(b'(http://[^:]+:\d+)/', stdoutdata)
+ self.assertIsNotNone(m, "Regex failed to match on %s" % stdoutdata)
+
+ stdoutdata = p.stdout.readline() # DANGER! ...
+ self.assertIn(
+ b"DANGER! For testing only, do not use in production",
+ stdoutdata)
+
+ stdoutdata = p.stdout.readline() # ***...
+
+ f = urlopen(m.group(1).decode('utf-8'))
+ self.assertEqual(output, f.read())
+
+ # Request again so that the application can force stderr.flush(),
+ # otherwise the log is buffered and the next readline() will hang.
+ urlopen(m.group(1).decode('utf-8'))
+
+ stdoutdata = p.stderr.readline()
+ # we should have logged an HTTP request, return code 200, that
+ # returned the right amount of bytes
+ status = '"GET / HTTP/1.1" 200 %d' % len(output)
+ self.assertIn(status.encode('utf-8'), stdoutdata)
+
+ def _check_wsgi_install_content(self, install_stdout):
+ for cmd_name in self.cmd_names:
+ install_txt = 'Installing %s script to %s' % (cmd_name,
+ self.temp_dir)
+ self.assertIn(install_txt, install_stdout)
+
+ cmd_filename = os.path.join(self.temp_dir, 'bin', cmd_name)
+
+ script_txt = open(cmd_filename, 'r').read()
+ self.assertNotIn('pkg_resources', script_txt)
+
+ main_block = """if __name__ == "__main__":
+ import argparse
+ import socket
+ import sys
+ import wsgiref.simple_server as wss"""
+
+ if cmd_name == 'pbr_test_wsgi':
+ app_name = "main"
+ else:
+ app_name = "WSGI.app"
+
+ starting_block = ("STARTING test server pbr_testpackage.wsgi."
+ "%s" % app_name)
+
+ else_block = """else:
+ application = None"""
+
+ self.assertIn(main_block, script_txt)
+ self.assertIn(starting_block, script_txt)
+ self.assertIn(else_block, script_txt)
+
+ def test_with_argument(self):
+ if os.name == 'nt':
+ self.skipTest('Windows support is passthrough')
+
+ stdout, _, return_code = self.run_setup(
+ 'install', '--prefix=%s' % self.temp_dir)
+
+ self._test_wsgi('pbr_test_wsgi', b'Foo Bar', ["--", "-c", "Foo Bar"])
--- /dev/null
+Changelog
+===========
+
+0.3 (unreleased)
+------------------
+
+- The ``glob_data_files`` hook became a pre-command hook for the install_data
+ command instead of being a setup-hook. This is to support the additional
+ functionality of requiring data_files with relative destination paths to be
+ install relative to the package's install path (i.e. site-packages).
+
+- Dropped support for and deprecated the easier_install custom command.
+ Although it should still work, it probably won't be used anymore for
+ stsci_python packages.
+
+- Added support for the ``build_optional_ext`` command, which replaces/extends
+ the default ``build_ext`` command. See the README for more details.
+
+- Added the ``tag_svn_revision`` setup_hook as a replacement for the
+ setuptools-specific tag_svn_revision option to the egg_info command. This
+ new hook is easier to use than the old tag_svn_revision option: It's
+ automatically enabled by the presence of ``.dev`` in the version string, and
+ disabled otherwise.
+
+- The ``svn_info_pre_hook`` and ``svn_info_post_hook`` have been replaced with
+ ``version_pre_command_hook`` and ``version_post_command_hook`` respectively.
+ However, a new ``version_setup_hook``, which has the same purpose, has been
+ added. It is generally easier to use and will give more consistent results
+ in that it will run every time setup.py is run, regardless of which command
+ is used. ``stsci.distutils`` itself uses this hook--see the `setup.cfg` file
+ and `stsci/distutils/__init__.py` for example usage.
+
+- Instead of creating an `svninfo.py` module, the new ``version_`` hooks create
+ a file called `version.py`. In addition to the SVN info that was included
+ in `svninfo.py`, it includes a ``__version__`` variable to be used by the
+ package's `__init__.py`. This allows there to be a hard-coded
+ ``__version__`` variable included in the source code, rather than using
+ pkg_resources to get the version.
+
+- In `version.py`, the variables previously named ``__svn_version__`` and
+ ``__full_svn_info__`` are now named ``__svn_revision__`` and
+ ``__svn_full_info__``.
+
+- Fixed a bug when using stsci.distutils in the installation of other packages
+ in the ``stsci.*`` namespace package. If stsci.distutils was not already
+ installed, and was downloaded automatically by distribute through the
+ setup_requires option, then ``stsci.distutils`` would fail to import. This
+ is because the way the namespace package (nspkg) mechanism currently works,
+ all packages belonging to the nspkg *must* be on the import path at initial
+ import time.
+
+ So when installing stsci.tools, for example, if ``stsci.tools`` is imported
+ from within the source code at install time, but before ``stsci.distutils``
+ is downloaded and added to the path, the ``stsci`` package is already
+ imported and can't be extended to include the path of ``stsci.distutils``
+ after the fact. The easiest way of dealing with this, it seems, is to
+ delete ``stsci`` from ``sys.modules``, which forces it to be reimported, now
+ the its ``__path__`` extended to include ``stsci.distutil``'s path.
+
+
+0.2.2 (2011-11-09)
+------------------
+
+- Fixed check for the issue205 bug on actual setuptools installs; before it
+ only worked on distribute. setuptools has the issue205 bug prior to version
+ 0.6c10.
+
+- Improved the fix for the issue205 bug, especially on setuptools.
+ setuptools, prior to 0.6c10, did not back of sys.modules either before
+ sandboxing, which causes serious problems. In fact, it's so bad that it's
+ not enough to add a sys.modules backup to the current sandbox: It's in fact
+ necessary to monkeypatch setuptools.sandbox.run_setup so that any subsequent
+ calls to it also back up sys.modules.
+
+
+0.2.1 (2011-09-02)
+------------------
+
+- Fixed the dependencies so that setuptools is requirement but 'distribute'
+ specifically. Previously installation could fail if users had plain
+ setuptools installed and not distribute
+
+0.2 (2011-08-23)
+------------------
+
+- Initial public release
--- /dev/null
+Copyright (C) 2005 Association of Universities for Research in Astronomy (AURA)
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above
+ copyright notice, this list of conditions and the following
+ disclaimer in the documentation and/or other materials provided
+ with the distribution.
+
+ 3. The name of AURA and its representatives may not be used to
+ endorse or promote products derived from this software without
+ specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+DAMAGE.
+
--- /dev/null
+include data_files/*
+exclude pbr_testpackage/extra.py
--- /dev/null
+Introduction
+============
+This package contains utilities used to package some of STScI's Python
+projects; specifically those projects that comprise stsci_python_ and
+Astrolib_.
+
+It currently consists mostly of some setup_hook scripts meant for use with
+`distutils2/packaging`_ and/or pbr_, and a customized easy_install command
+meant for use with distribute_.
+
+This package is not meant for general consumption, though it might be worth
+looking at for examples of how to do certain things with your own packages, but
+YMMV.
+
+Features
+========
+
+Hook Scripts
+------------
+Currently the main features of this package are a couple of setup_hook scripts.
+In distutils2, a setup_hook is a script that runs at the beginning of any
+pysetup command, and can modify the package configuration read from setup.cfg.
+There are also pre- and post-command hooks that only run before/after a
+specific setup command (eg. build_ext, install) is run.
+
+stsci.distutils.hooks.use_packages_root
+'''''''''''''''''''''''''''''''''''''''
+If using the ``packages_root`` option under the ``[files]`` section of
+setup.cfg, this hook will add that path to ``sys.path`` so that modules in your
+package can be imported and used in setup. This can be used even if
+``packages_root`` is not specified--in this case it adds ``''`` to
+``sys.path``.
+
+stsci.distutils.hooks.version_setup_hook
+''''''''''''''''''''''''''''''''''''''''
+Creates a Python module called version.py which currently contains four
+variables:
+
+* ``__version__`` (the release version)
+* ``__svn_revision__`` (the SVN revision info as returned by the ``svnversion``
+ command)
+* ``__svn_full_info__`` (as returned by the ``svn info`` command)
+* ``__setup_datetime__`` (the date and time that setup.py was last run).
+
+These variables can be imported in the package's `__init__.py` for degugging
+purposes. The version.py module will *only* be created in a package that
+imports from the version module in its `__init__.py`. It should be noted that
+this is generally preferable to writing these variables directly into
+`__init__.py`, since this provides more control and is less likely to
+unexpectedly break things in `__init__.py`.
+
+stsci.distutils.hooks.version_pre_command_hook
+''''''''''''''''''''''''''''''''''''''''''''''
+Identical to version_setup_hook, but designed to be used as a pre-command
+hook.
+
+stsci.distutils.hooks.version_post_command_hook
+'''''''''''''''''''''''''''''''''''''''''''''''
+The complement to version_pre_command_hook. This will delete any version.py
+files created during a build in order to prevent them from cluttering an SVN
+working copy (note, however, that version.py is *not* deleted from the build/
+directory, so a copy of it is still preserved). It will also not be deleted
+if the current directory is not an SVN working copy. For example, if source
+code extracted from a source tarball it will be preserved.
+
+stsci.distutils.hooks.tag_svn_revision
+''''''''''''''''''''''''''''''''''''''
+A setup_hook to add the SVN revision of the current working copy path to the
+package version string, but only if the version ends in .dev.
+
+For example, ``mypackage-1.0.dev`` becomes ``mypackage-1.0.dev1234``. This is
+in accordance with the version string format standardized by PEP 386.
+
+This should be used as a replacement for the ``tag_svn_revision`` option to
+the egg_info command. This hook is more compatible with packaging/distutils2,
+which does not include any VCS support. This hook is also more flexible in
+that it turns the revision number on/off depending on the presence of ``.dev``
+in the version string, so that it's not automatically added to the version in
+final releases.
+
+This hook does require the ``svnversion`` command to be available in order to
+work. It does not examine the working copy metadata directly.
+
+stsci.distutils.hooks.numpy_extension_hook
+''''''''''''''''''''''''''''''''''''''''''
+This is a pre-command hook for the build_ext command. To use it, add a
+``[build_ext]`` section to your setup.cfg, and add to it::
+
+ pre-hook.numpy-extension-hook = stsci.distutils.hooks.numpy_extension_hook
+
+This hook must be used to build extension modules that use Numpy. The primary
+side-effect of this hook is to add the correct numpy include directories to
+`include_dirs`. To use it, add 'numpy' to the 'include-dirs' option of each
+extension module that requires numpy to build. The value 'numpy' will be
+replaced with the actual path to the numpy includes.
+
+stsci.distutils.hooks.is_display_option
+'''''''''''''''''''''''''''''''''''''''
+This is not actually a hook, but is a useful utility function that can be used
+in writing other hooks. Basically, it returns ``True`` if setup.py was run
+with a "display option" such as --version or --help. This can be used to
+prevent your hook from running in such cases.
+
+stsci.distutils.hooks.glob_data_files
+'''''''''''''''''''''''''''''''''''''
+A pre-command hook for the install_data command. Allows filename wildcards as
+understood by ``glob.glob()`` to be used in the data_files option. This hook
+must be used in order to have this functionality since it does not normally
+exist in distutils.
+
+This hook also ensures that data files are installed relative to the package
+path. data_files shouldn't normally be installed this way, but the
+functionality is required for a few special cases.
+
+
+Commands
+--------
+build_optional_ext
+''''''''''''''''''
+This serves as an optional replacement for the default built_ext command,
+which compiles C extension modules. Its purpose is to allow extension modules
+to be *optional*, so that if their build fails the rest of the package is
+still allowed to be built and installed. This can be used when an extension
+module is not definitely required to use the package.
+
+To use this custom command, add::
+
+ commands = stsci.distutils.command.build_optional_ext.build_optional_ext
+
+under the ``[global]`` section of your package's setup.cfg. Then, to mark
+an individual extension module as optional, under the setup.cfg section for
+that extension add::
+
+ optional = True
+
+Optionally, you may also add a custom failure message by adding::
+
+ fail_message = The foobar extension module failed to compile.
+ This could be because you lack such and such headers.
+ This package will still work, but such and such features
+ will be disabled.
+
+
+.. _stsci_python: http://www.stsci.edu/resources/software_hardware/pyraf/stsci_python
+.. _Astrolib: http://www.scipy.org/AstroLib/
+.. _distutils2/packaging: http://distutils2.notmyidea.org/
+.. _d2to1: http://pypi.python.org/pypi/d2to1
+.. _distribute: http://pypi.python.org/pypi/distribute
--- /dev/null
+# -*- coding: utf-8 -*-
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import os
+import sys
+
+sys.path.insert(0, os.path.abspath('../..'))
+# -- General configuration ----------------------------------------------------
+
+# Add any Sphinx extension module names here, as strings. They can be
+# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones.
+extensions = [
+ 'sphinx.ext.autodoc',
+ #'sphinx.ext.intersphinx',
+]
+
+# autodoc generation is a bit aggressive and a nuisance when doing heavy
+# text edit cycles.
+# execute "export SPHINX_DEBUG=1" in your terminal to disable
+
+# The suffix of source filenames.
+source_suffix = '.rst'
+
+# The master toctree document.
+master_doc = 'index'
+
+# General information about the project.
+project = u'testpackage'
+copyright = u'2013, OpenStack Foundation'
+
+# If true, '()' will be appended to :func: etc. cross-reference text.
+add_function_parentheses = True
+
+# If true, the current module name will be prepended to all description
+# unit titles (such as .. function::).
+add_module_names = True
+
+# The name of the Pygments (syntax highlighting) style to use.
+pygments_style = 'sphinx'
+
+# -- Options for HTML output --------------------------------------------------
+
+# The theme to use for HTML and HTML Help pages. Major themes that come with
+# Sphinx are currently 'default' and 'sphinxdoc'.
+# html_theme_path = ["."]
+# html_theme = '_theme'
+# html_static_path = ['static']
+
+# Output file base name for HTML help builder.
+htmlhelp_basename = '%sdoc' % project
+
+# Grouping the document tree into LaTeX files. List of tuples
+# (source start file, target name, title, author, documentclass
+# [howto/manual]).
+latex_documents = [
+ ('index',
+ '%s.tex' % project,
+ u'%s Documentation' % project,
+ u'OpenStack Foundation', 'manual'),
+]
+
+# Example configuration for intersphinx: refer to the Python standard library.
+#intersphinx_mapping = {'http://docs.python.org/': None}
--- /dev/null
+.. testpackage documentation master file, created by
+ sphinx-quickstart on Tue Jul 9 22:26:36 2013.
+ You can adapt this file completely to your liking, but it should at least
+ contain the root `toctree` directive.
+
+Welcome to testpackage's documentation!
+========================================================
+
+Contents:
+
+.. toctree::
+ :maxdepth: 2
+
+ installation
+ usage
+
+Indices and tables
+==================
+
+* :ref:`genindex`
+* :ref:`modindex`
+* :ref:`search`
+
--- /dev/null
+============
+Installation
+============
+
+At the command line::
+
+ $ pip install testpackage
+
+Or, if you have virtualenvwrapper installed::
+
+ $ mkvirtualenv testpackage
+ $ pip install testpackage
--- /dev/null
+========
+Usage
+========
+
+To use testpackage in a project::
+
+ import testpackage
--- /dev/null
+import pbr.version
+
+__version__ = pbr.version.VersionInfo('pbr_testpackage').version_string()
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+
+from distutils.command import build_py
+
+
+def test_hook_1(config):
+ print('test_hook_1')
+
+
+def test_hook_2(config):
+ print('test_hook_2')
+
+
+class test_command(build_py.build_py):
+ command_name = 'build_py'
+
+ def run(self):
+ print('Running custom build_py command.')
+ return build_py.build_py.run(self)
+
+
+def test_pre_hook(cmdobj):
+ print('build_ext pre-hook')
+
+
+def test_post_hook(cmdobj):
+ print('build_ext post-hook')
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import print_function
+
+
+def main():
+ print("PBR Test Command")
+
+
+class Foo(object):
+
+ @classmethod
+ def bar(self):
+ print("PBR Test Command - with class!")
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+from __future__ import print_function
+
+import argparse
+import functools
+import sys
+
+
+def application(env, start_response, data):
+ sys.stderr.flush() # Force the previous request log to be written.
+ start_response('200 OK', [('Content-Type', 'text/html')])
+ return [data.encode('utf-8')]
+
+
+def main():
+ parser = argparse.ArgumentParser(description='Return a string.')
+ parser.add_argument('--content', '-c', help='String returned',
+ default='Hello World')
+ args = parser.parse_args()
+ return functools.partial(application, data=args.content)
+
+
+class WSGI(object):
+
+ @classmethod
+ def app(self):
+ return functools.partial(application, data='Hello World')
--- /dev/null
+[metadata]
+name = pbr_testpackage
+# TODO(lifeless) we should inject this as needed otherwise we're not truely
+# testing postversioned codepaths.
+version = 0.1.dev
+author = OpenStack
+author-email = openstack-dev@lists.openstack.org
+home-page = http://pypi.python.org/pypi/pbr
+summary = Test package for testing pbr
+description-file =
+ README.txt
+ CHANGES.txt
+requires-python = >=2.5
+
+requires-dist =
+ setuptools
+
+classifier =
+ Development Status :: 3 - Alpha
+ Intended Audience :: Developers
+ License :: OSI Approved :: BSD License
+ Programming Language :: Python
+ Topic :: Scientific/Engineering
+ Topic :: Software Development :: Build Tools
+ Topic :: Software Development :: Libraries :: Python Modules
+ Topic :: System :: Archiving :: Packaging
+
+keywords = packaging, distutils, setuptools
+
+[files]
+packages = pbr_testpackage
+package-data = testpackage = package_data/*.txt
+data-files = testpackage/data_files = data_files/*
+extra-files = extra-file.txt
+
+[entry_points]
+console_scripts =
+ pbr_test_cmd = pbr_testpackage.cmd:main
+ pbr_test_cmd_with_class = pbr_testpackage.cmd:Foo.bar
+
+wsgi_scripts =
+ pbr_test_wsgi = pbr_testpackage.wsgi:main
+ pbr_test_wsgi_with_class = pbr_testpackage.wsgi:WSGI.app
+
+[extension=pbr_testpackage.testext]
+sources = src/testext.c
+optional = True
+
+[global]
+#setup-hooks =
+# pbr_testpackage._setup_hooks.test_hook_1
+# pbr_testpackage._setup_hooks.test_hook_2
+commands = pbr_testpackage._setup_hooks.test_command
+
+[build_ext]
+#pre-hook.test_pre_hook = pbr_testpackage._setup_hooks.test_pre_hook
+#post-hook.test_post_hook = pbr_testpackage._setup_hooks.test_post_hook
--- /dev/null
+#!/usr/bin/env python
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import setuptools
+
+setuptools.setup(
+ setup_requires=['pbr'],
+ pbr=True,
+)
--- /dev/null
+#include <Python.h>
+
+
+static PyMethodDef TestextMethods[] = {
+ {NULL, NULL, 0, NULL}
+};
+
+
+#if PY_MAJOR_VERSION >=3
+static struct PyModuleDef testextmodule = {
+ PyModuleDef_HEAD_INIT, /* This should correspond to a PyModuleDef_Base type */
+ "testext", /* This is the module name */
+ "Test extension module", /* This is the module docstring */
+ -1, /* This defines the size of the module and says everything is global */
+ TestextMethods /* This is the method definition */
+};
+
+PyObject*
+PyInit_testext(void)
+{
+ return PyModule_Create(&testextmodule);
+}
+#else
+PyMODINIT_FUNC
+inittestext(void)
+{
+ Py_InitModule("testext", TestextMethods);
+}
+#endif
--- /dev/null
+ordereddict;python_version=='2.6'
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+
+import contextlib
+import os
+import shutil
+import stat
+import sys
+
+try:
+ import ConfigParser as configparser
+except ImportError:
+ import configparser
+
+
+@contextlib.contextmanager
+def open_config(filename):
+ if sys.version_info >= (3, 2):
+ cfg = configparser.ConfigParser()
+ else:
+ cfg = configparser.SafeConfigParser()
+ cfg.read(filename)
+ yield cfg
+ with open(filename, 'w') as fp:
+ cfg.write(fp)
+
+
+def rmtree(path):
+ """shutil.rmtree() with error handler.
+
+ Handle 'access denied' from trying to delete read-only files.
+ """
+
+ def onerror(func, path, exc_info):
+ if not os.access(path, os.W_OK):
+ os.chmod(path, stat.S_IWUSR)
+ func(path)
+ else:
+ raise
+
+ return shutil.rmtree(path, onerror=onerror)
--- /dev/null
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+# Copyright (C) 2013 Association of Universities for Research in Astronomy
+# (AURA)
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above
+# copyright notice, this list of conditions and the following
+# disclaimer in the documentation and/or other materials provided
+# with the distribution.
+#
+# 3. The name of AURA and its representatives may not be used to
+# endorse or promote products derived from this software without
+# specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED
+# WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
+# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+
+"""The code in this module is mostly copy/pasted out of the distutils2 source
+code, as recommended by Tarek Ziade. As such, it may be subject to some change
+as distutils2 development continues, and will have to be kept up to date.
+
+I didn't want to use it directly from distutils2 itself, since I do not want it
+to be an installation dependency for our packages yet--it is still too unstable
+(the latest version on PyPI doesn't even install).
+"""
+
+# These first two imports are not used, but are needed to get around an
+# irritating Python bug that can crop up when using ./setup.py test.
+# See: http://www.eby-sarna.com/pipermail/peak/2010-May/003355.html
+try:
+ import multiprocessing # flake8: noqa
+except ImportError:
+ pass
+import logging # flake8: noqa
+
+import os
+import re
+import sys
+import traceback
+
+from collections import defaultdict
+
+import distutils.ccompiler
+import pkg_resources
+
+from distutils import log
+from distutils import errors
+from setuptools.command.egg_info import manifest_maker
+from setuptools import dist as st_dist
+from setuptools import extension
+
+try:
+ import ConfigParser as configparser
+except ImportError:
+ import configparser
+
+from pbr import extra_files
+import pbr.hooks
+
+# A simplified RE for this; just checks that the line ends with version
+# predicates in ()
+_VERSION_SPEC_RE = re.compile(r'\s*(.*?)\s*\((.*)\)\s*$')
+
+
+# Mappings from setup() keyword arguments to setup.cfg options;
+# The values are (section, option) tuples, or simply (section,) tuples if
+# the option has the same name as the setup() argument
+D1_D2_SETUP_ARGS = {
+ "name": ("metadata",),
+ "version": ("metadata",),
+ "author": ("metadata",),
+ "author_email": ("metadata",),
+ "maintainer": ("metadata",),
+ "maintainer_email": ("metadata",),
+ "url": ("metadata", "home_page"),
+ "description": ("metadata", "summary"),
+ "keywords": ("metadata",),
+ "long_description": ("metadata", "description"),
+ "download_url": ("metadata",),
+ "classifiers": ("metadata", "classifier"),
+ "platforms": ("metadata", "platform"), # **
+ "license": ("metadata",),
+ # Use setuptools install_requires, not
+ # broken distutils requires
+ "install_requires": ("metadata", "requires_dist"),
+ "setup_requires": ("metadata", "setup_requires_dist"),
+ "provides": ("metadata", "provides_dist"), # **
+ "obsoletes": ("metadata", "obsoletes_dist"), # **
+ "package_dir": ("files", 'packages_root'),
+ "packages": ("files",),
+ "package_data": ("files",),
+ "namespace_packages": ("files",),
+ "data_files": ("files",),
+ "scripts": ("files",),
+ "py_modules": ("files", "modules"), # **
+ "cmdclass": ("global", "commands"),
+ # Not supported in distutils2, but provided for
+ # backwards compatibility with setuptools
+ "use_2to3": ("backwards_compat", "use_2to3"),
+ "zip_safe": ("backwards_compat", "zip_safe"),
+ "tests_require": ("backwards_compat", "tests_require"),
+ "dependency_links": ("backwards_compat",),
+ "include_package_data": ("backwards_compat",),
+}
+
+# setup() arguments that can have multiple values in setup.cfg
+MULTI_FIELDS = ("classifiers",
+ "platforms",
+ "install_requires",
+ "provides",
+ "obsoletes",
+ "namespace_packages",
+ "packages",
+ "package_data",
+ "data_files",
+ "scripts",
+ "py_modules",
+ "dependency_links",
+ "setup_requires",
+ "tests_require",
+ "cmdclass")
+
+# setup() arguments that contain boolean values
+BOOL_FIELDS = ("use_2to3", "zip_safe", "include_package_data")
+
+
+CSV_FIELDS = ("keywords",)
+
+
+def resolve_name(name):
+ """Resolve a name like ``module.object`` to an object and return it.
+
+ Raise ImportError if the module or name is not found.
+ """
+
+ parts = name.split('.')
+ cursor = len(parts) - 1
+ module_name = parts[:cursor]
+ attr_name = parts[-1]
+
+ while cursor > 0:
+ try:
+ ret = __import__('.'.join(module_name), fromlist=[attr_name])
+ break
+ except ImportError:
+ if cursor == 0:
+ raise
+ cursor -= 1
+ module_name = parts[:cursor]
+ attr_name = parts[cursor]
+ ret = ''
+
+ for part in parts[cursor:]:
+ try:
+ ret = getattr(ret, part)
+ except AttributeError:
+ raise ImportError(name)
+
+ return ret
+
+
+def cfg_to_args(path='setup.cfg', script_args=()):
+ """Distutils2 to distutils1 compatibility util.
+
+ This method uses an existing setup.cfg to generate a dictionary of
+ keywords that can be used by distutils.core.setup(kwargs**).
+
+ :param file:
+ The setup.cfg path.
+ :parm script_args:
+ List of commands setup.py was called with.
+ :raises DistutilsFileError:
+ When the setup.cfg file is not found.
+ """
+
+ # The method source code really starts here.
+ if sys.version_info >= (3, 2):
+ parser = configparser.ConfigParser()
+ else:
+ parser = configparser.SafeConfigParser()
+ if not os.path.exists(path):
+ raise errors.DistutilsFileError("file '%s' does not exist" %
+ os.path.abspath(path))
+ parser.read(path)
+ config = {}
+ for section in parser.sections():
+ config[section] = dict()
+ for k, value in parser.items(section):
+ config[section][k.replace('-', '_')] = value
+
+ # Run setup_hooks, if configured
+ setup_hooks = has_get_option(config, 'global', 'setup_hooks')
+ package_dir = has_get_option(config, 'files', 'packages_root')
+
+ # Add the source package directory to sys.path in case it contains
+ # additional hooks, and to make sure it's on the path before any existing
+ # installations of the package
+ if package_dir:
+ package_dir = os.path.abspath(package_dir)
+ sys.path.insert(0, package_dir)
+
+ try:
+ if setup_hooks:
+ setup_hooks = [
+ hook for hook in split_multiline(setup_hooks)
+ if hook != 'pbr.hooks.setup_hook']
+ for hook in setup_hooks:
+ hook_fn = resolve_name(hook)
+ try :
+ hook_fn(config)
+ except SystemExit:
+ log.error('setup hook %s terminated the installation')
+ except:
+ e = sys.exc_info()[1]
+ log.error('setup hook %s raised exception: %s\n' %
+ (hook, e))
+ log.error(traceback.format_exc())
+ sys.exit(1)
+
+ # Run the pbr hook
+ pbr.hooks.setup_hook(config)
+
+ kwargs = setup_cfg_to_setup_kwargs(config, script_args)
+
+ # Set default config overrides
+ kwargs['include_package_data'] = True
+ kwargs['zip_safe'] = False
+
+ register_custom_compilers(config)
+
+ ext_modules = get_extension_modules(config)
+ if ext_modules:
+ kwargs['ext_modules'] = ext_modules
+
+ entry_points = get_entry_points(config)
+ if entry_points:
+ kwargs['entry_points'] = entry_points
+
+ wrap_commands(kwargs)
+
+ # Handle the [files]/extra_files option
+ files_extra_files = has_get_option(config, 'files', 'extra_files')
+ if files_extra_files:
+ extra_files.set_extra_files(split_multiline(files_extra_files))
+
+ finally:
+ # Perform cleanup if any paths were added to sys.path
+ if package_dir:
+ sys.path.pop(0)
+
+ return kwargs
+
+
+def setup_cfg_to_setup_kwargs(config, script_args=()):
+ """Processes the setup.cfg options and converts them to arguments accepted
+ by setuptools' setup() function.
+ """
+
+ kwargs = {}
+
+ # Temporarily holds install_requires and extra_requires while we
+ # parse env_markers.
+ all_requirements = {}
+
+ for arg in D1_D2_SETUP_ARGS:
+ if len(D1_D2_SETUP_ARGS[arg]) == 2:
+ # The distutils field name is different than distutils2's.
+ section, option = D1_D2_SETUP_ARGS[arg]
+
+ elif len(D1_D2_SETUP_ARGS[arg]) == 1:
+ # The distutils field name is the same thant distutils2's.
+ section = D1_D2_SETUP_ARGS[arg][0]
+ option = arg
+
+ in_cfg_value = has_get_option(config, section, option)
+ if not in_cfg_value:
+ # There is no such option in the setup.cfg
+ if arg == "long_description":
+ in_cfg_value = has_get_option(config, section,
+ "description_file")
+ if in_cfg_value:
+ in_cfg_value = split_multiline(in_cfg_value)
+ value = ''
+ for filename in in_cfg_value:
+ description_file = open(filename)
+ try:
+ value += description_file.read().strip() + '\n\n'
+ finally:
+ description_file.close()
+ in_cfg_value = value
+ else:
+ continue
+
+ if arg in CSV_FIELDS:
+ in_cfg_value = split_csv(in_cfg_value)
+ if arg in MULTI_FIELDS:
+ in_cfg_value = split_multiline(in_cfg_value)
+ elif arg in BOOL_FIELDS:
+ # Provide some flexibility here...
+ if in_cfg_value.lower() in ('true', 't', '1', 'yes', 'y'):
+ in_cfg_value = True
+ else:
+ in_cfg_value = False
+
+ if in_cfg_value:
+ if arg in ('install_requires', 'tests_require'):
+ # Replaces PEP345-style version specs with the sort expected by
+ # setuptools
+ in_cfg_value = [_VERSION_SPEC_RE.sub(r'\1\2', pred)
+ for pred in in_cfg_value]
+ if arg == 'install_requires':
+ # Split install_requires into package,env_marker tuples
+ # These will be re-assembled later
+ install_requires = []
+ requirement_pattern = '(?P<package>[^;]*);?(?P<env_marker>[^#]*?)(?:\s*#.*)?$'
+ for requirement in in_cfg_value:
+ m = re.match(requirement_pattern, requirement)
+ requirement_package = m.group('package').strip()
+ env_marker = m.group('env_marker').strip()
+ install_requires.append((requirement_package,env_marker))
+ all_requirements[''] = install_requires
+ elif arg == 'package_dir':
+ in_cfg_value = {'': in_cfg_value}
+ elif arg in ('package_data', 'data_files'):
+ data_files = {}
+ firstline = True
+ prev = None
+ for line in in_cfg_value:
+ if '=' in line:
+ key, value = line.split('=', 1)
+ key, value = (key.strip(), value.strip())
+ if key in data_files:
+ # Multiple duplicates of the same package name;
+ # this is for backwards compatibility of the old
+ # format prior to d2to1 0.2.6.
+ prev = data_files[key]
+ prev.extend(value.split())
+ else:
+ prev = data_files[key.strip()] = value.split()
+ elif firstline:
+ raise errors.DistutilsOptionError(
+ 'malformed package_data first line %r (misses '
+ '"=")' % line)
+ else:
+ prev.extend(line.strip().split())
+ firstline = False
+ if arg == 'data_files':
+ # the data_files value is a pointlessly different structure
+ # from the package_data value
+ data_files = data_files.items()
+ in_cfg_value = data_files
+ elif arg == 'cmdclass':
+ cmdclass = {}
+ dist = st_dist.Distribution()
+ for cls_name in in_cfg_value:
+ cls = resolve_name(cls_name)
+ cmd = cls(dist)
+ cmdclass[cmd.get_command_name()] = cls
+ in_cfg_value = cmdclass
+
+ kwargs[arg] = in_cfg_value
+
+ # Transform requirements with embedded environment markers to
+ # setuptools' supported marker-per-requirement format.
+ #
+ # install_requires are treated as a special case of extras, before
+ # being put back in the expected place
+ #
+ # fred =
+ # foo:marker
+ # bar
+ # -> {'fred': ['bar'], 'fred:marker':['foo']}
+
+ if 'extras' in config:
+ requirement_pattern = '(?P<package>[^:]*):?(?P<env_marker>[^#]*?)(?:\s*#.*)?$'
+ extras = config['extras']
+ for extra in extras:
+ extra_requirements = []
+ requirements = split_multiline(extras[extra])
+ for requirement in requirements:
+ m = re.match(requirement_pattern, requirement)
+ extras_value = m.group('package').strip()
+ env_marker = m.group('env_marker')
+ extra_requirements.append((extras_value,env_marker))
+ all_requirements[extra] = extra_requirements
+
+ # Transform the full list of requirements into:
+ # - install_requires, for those that have no extra and no
+ # env_marker
+ # - named extras, for those with an extra name (which may include
+ # an env_marker)
+ # - and as a special case, install_requires with an env_marker are
+ # treated as named extras where the name is the empty string
+
+ extras_require = {}
+ for req_group in all_requirements:
+ for requirement, env_marker in all_requirements[req_group]:
+ if env_marker:
+ extras_key = '%s:(%s)' % (req_group, env_marker)
+ # We do not want to poison wheel creation with locally
+ # evaluated markers. sdists always re-create the egg_info
+ # and as such do not need guarded, and pip will never call
+ # multiple setup.py commands at once.
+ if 'bdist_wheel' not in script_args:
+ try:
+ if pkg_resources.evaluate_marker('(%s)' % env_marker):
+ extras_key = req_group
+ except SyntaxError:
+ log.error(
+ "Marker evaluation failed, see the following "
+ "error. For more information see: "
+ "http://docs.openstack.org/"
+ "developer/pbr/compatibility.html#evaluate-marker"
+ )
+ raise
+ else:
+ extras_key = req_group
+ extras_require.setdefault(extras_key, []).append(requirement)
+
+ kwargs['install_requires'] = extras_require.pop('', [])
+ kwargs['extras_require'] = extras_require
+
+ return kwargs
+
+
+def register_custom_compilers(config):
+ """Handle custom compilers; this has no real equivalent in distutils, where
+ additional compilers could only be added programmatically, so we have to
+ hack it in somehow.
+ """
+
+ compilers = has_get_option(config, 'global', 'compilers')
+ if compilers:
+ compilers = split_multiline(compilers)
+ for compiler in compilers:
+ compiler = resolve_name(compiler)
+
+ # In distutils2 compilers these class attributes exist; for
+ # distutils1 we just have to make something up
+ if hasattr(compiler, 'name'):
+ name = compiler.name
+ else:
+ name = compiler.__name__
+ if hasattr(compiler, 'description'):
+ desc = compiler.description
+ else:
+ desc = 'custom compiler %s' % name
+
+ module_name = compiler.__module__
+ # Note; this *will* override built in compilers with the same name
+ # TODO: Maybe display a warning about this?
+ cc = distutils.ccompiler.compiler_class
+ cc[name] = (module_name, compiler.__name__, desc)
+
+ # HACK!!!! Distutils assumes all compiler modules are in the
+ # distutils package
+ sys.modules['distutils.' + module_name] = sys.modules[module_name]
+
+
+def get_extension_modules(config):
+ """Handle extension modules"""
+
+ EXTENSION_FIELDS = ("sources",
+ "include_dirs",
+ "define_macros",
+ "undef_macros",
+ "library_dirs",
+ "libraries",
+ "runtime_library_dirs",
+ "extra_objects",
+ "extra_compile_args",
+ "extra_link_args",
+ "export_symbols",
+ "swig_opts",
+ "depends")
+
+ ext_modules = []
+ for section in config:
+ if ':' in section:
+ labels = section.split(':', 1)
+ else:
+ # Backwards compatibility for old syntax; don't use this though
+ labels = section.split('=', 1)
+ labels = [l.strip() for l in labels]
+ if (len(labels) == 2) and (labels[0] == 'extension'):
+ ext_args = {}
+ for field in EXTENSION_FIELDS:
+ value = has_get_option(config, section, field)
+ # All extension module options besides name can have multiple
+ # values
+ if not value:
+ continue
+ value = split_multiline(value)
+ if field == 'define_macros':
+ macros = []
+ for macro in value:
+ macro = macro.split('=', 1)
+ if len(macro) == 1:
+ macro = (macro[0].strip(), None)
+ else:
+ macro = (macro[0].strip(), macro[1].strip())
+ macros.append(macro)
+ value = macros
+ ext_args[field] = value
+ if ext_args:
+ if 'name' not in ext_args:
+ ext_args['name'] = labels[1]
+ ext_modules.append(extension.Extension(ext_args.pop('name'),
+ **ext_args))
+ return ext_modules
+
+
+def get_entry_points(config):
+ """Process the [entry_points] section of setup.cfg to handle setuptools
+ entry points. This is, of course, not a standard feature of
+ distutils2/packaging, but as there is not currently a standard alternative
+ in packaging, we provide support for them.
+ """
+
+ if not 'entry_points' in config:
+ return {}
+
+ return dict((option, split_multiline(value))
+ for option, value in config['entry_points'].items())
+
+
+def wrap_commands(kwargs):
+ dist = st_dist.Distribution()
+
+ # This should suffice to get the same config values and command classes
+ # that the actual Distribution will see (not counting cmdclass, which is
+ # handled below)
+ dist.parse_config_files()
+
+ # Setuptools doesn't patch get_command_list, and as such we do not get
+ # extra commands from entry_points. As we need to be compatable we deal
+ # with this here.
+ for ep in pkg_resources.iter_entry_points('distutils.commands'):
+ if ep.name not in dist.cmdclass:
+ if hasattr(ep, 'resolve'):
+ cmdclass = ep.resolve()
+ else:
+ # Old setuptools does not have ep.resolve, and load with
+ # arguments is depricated in 11+. Use resolve, 12+, if we
+ # can, otherwise fall back to load.
+ # Setuptools 11 will throw a deprication warning, as it
+ # uses _load instead of resolve.
+ cmdclass = ep.load(False)
+ dist.cmdclass[ep.name] = cmdclass
+
+ for cmd, _ in dist.get_command_list():
+ hooks = {}
+ for opt, val in dist.get_option_dict(cmd).items():
+ val = val[1]
+ if opt.startswith('pre_hook.') or opt.startswith('post_hook.'):
+ hook_type, alias = opt.split('.', 1)
+ hook_dict = hooks.setdefault(hook_type, {})
+ hook_dict[alias] = val
+ if not hooks:
+ continue
+
+ if 'cmdclass' in kwargs and cmd in kwargs['cmdclass']:
+ cmdclass = kwargs['cmdclass'][cmd]
+ else:
+ cmdclass = dist.get_command_class(cmd)
+
+ new_cmdclass = wrap_command(cmd, cmdclass, hooks)
+ kwargs.setdefault('cmdclass', {})[cmd] = new_cmdclass
+
+
+def wrap_command(cmd, cmdclass, hooks):
+ def run(self, cmdclass=cmdclass):
+ self.run_command_hooks('pre_hook')
+ cmdclass.run(self)
+ self.run_command_hooks('post_hook')
+
+ return type(cmd, (cmdclass, object),
+ {'run': run, 'run_command_hooks': run_command_hooks,
+ 'pre_hook': hooks.get('pre_hook'),
+ 'post_hook': hooks.get('post_hook')})
+
+
+def run_command_hooks(cmd_obj, hook_kind):
+ """Run hooks registered for that command and phase.
+
+ *cmd_obj* is a finalized command object; *hook_kind* is either
+ 'pre_hook' or 'post_hook'.
+ """
+
+ if hook_kind not in ('pre_hook', 'post_hook'):
+ raise ValueError('invalid hook kind: %r' % hook_kind)
+
+ hooks = getattr(cmd_obj, hook_kind, None)
+
+ if hooks is None:
+ return
+
+ for hook in hooks.values():
+ if isinstance(hook, str):
+ try:
+ hook_obj = resolve_name(hook)
+ except ImportError:
+ err = sys.exc_info()[1] # For py3k
+ raise errors.DistutilsModuleError('cannot find hook %s: %s' %
+ (hook,err))
+ else:
+ hook_obj = hook
+
+ if not hasattr(hook_obj, '__call__'):
+ raise errors.DistutilsOptionError('hook %r is not callable' % hook)
+
+ log.info('running %s %s for command %s',
+ hook_kind, hook, cmd_obj.get_command_name())
+
+ try :
+ hook_obj(cmd_obj)
+ except:
+ e = sys.exc_info()[1]
+ log.error('hook %s raised exception: %s\n' % (hook, e))
+ log.error(traceback.format_exc())
+ sys.exit(1)
+
+
+def has_get_option(config, section, option):
+ if section in config and option in config[section]:
+ return config[section][option]
+ else:
+ return False
+
+
+def split_multiline(value):
+ """Special behaviour when we have a multi line options"""
+
+ value = [element for element in
+ (line.strip() for line in value.split('\n'))
+ if element]
+ return value
+
+
+def split_csv(value):
+ """Special behaviour when we have a comma separated options"""
+
+ value = [element for element in
+ (chunk.strip() for chunk in value.split(','))
+ if element]
+ return value
+
+
+def monkeypatch_method(cls):
+ """A function decorator to monkey-patch a method of the same name on the
+ given class.
+ """
+
+ def wrapper(func):
+ orig = getattr(cls, func.__name__, None)
+ if orig and not hasattr(orig, '_orig'): # Already patched
+ setattr(func, '_orig', orig)
+ setattr(cls, func.__name__, func)
+ return func
+
+ return wrapper
+
+
+# The following classes are used to hack Distribution.command_options a bit
+class DefaultGetDict(defaultdict):
+ """Like defaultdict, but the get() method also sets and returns the default
+ value.
+ """
+
+ def get(self, key, default=None):
+ if default is None:
+ default = self.default_factory()
+ return super(DefaultGetDict, self).setdefault(key, default)
+
+
+class IgnoreDict(dict):
+ """A dictionary that ignores any insertions in which the key is a string
+ matching any string in `ignore`. The ignore list can also contain wildcard
+ patterns using '*'.
+ """
+
+ def __init__(self, ignore):
+ self.__ignore = re.compile(r'(%s)' % ('|'.join(
+ [pat.replace('*', '.*')
+ for pat in ignore])))
+
+ def __setitem__(self, key, val):
+ if self.__ignore.match(key):
+ return
+ super(IgnoreDict, self).__setitem__(key, val)
--- /dev/null
+
+# Copyright 2012 OpenStack Foundation
+# Copyright 2012-2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+"""
+Utilities for consuming the version from pkg_resources.
+"""
+
+import itertools
+import operator
+import sys
+
+import pkg_resources
+
+
+def _is_int(string):
+ try:
+ int(string)
+ return True
+ except ValueError:
+ return False
+
+
+class SemanticVersion(object):
+ """A pure semantic version independent of serialisation.
+
+ See the pbr doc 'semver' for details on the semantics.
+ """
+
+ def __init__(
+ self, major, minor=0, patch=0, prerelease_type=None,
+ prerelease=None, dev_count=None):
+ """Create a SemanticVersion.
+
+ :param major: Major component of the version.
+ :param minor: Minor component of the version. Defaults to 0.
+ :param patch: Patch level component. Defaults to 0.
+ :param prerelease_type: What sort of prerelease version this is -
+ one of a(alpha), b(beta) or rc(release candidate).
+ :param prerelease: For prerelease versions, what number prerelease.
+ Defaults to 0.
+ :param dev_count: How many commits since the last release.
+ """
+ self._major = major
+ self._minor = minor
+ self._patch = patch
+ self._prerelease_type = prerelease_type
+ self._prerelease = prerelease
+ if self._prerelease_type and not self._prerelease:
+ self._prerelease = 0
+ self._dev_count = dev_count or 0 # Normalise 0 to None.
+
+ def __eq__(self, other):
+ if not isinstance(other, SemanticVersion):
+ return False
+ return self.__dict__ == other.__dict__
+
+ def __hash__(self):
+ return sum(map(hash, self.__dict__.values()))
+
+ def _sort_key(self):
+ """Return a key for sorting SemanticVersion's on."""
+ # key things:
+ # - final is after rc's, so we make that a/b/rc/z
+ # - dev==None is after all other devs, so we use sys.maxsize there.
+ # - unqualified dev releases come before any pre-releases.
+ # So we do:
+ # (major, minor, patch) - gets the major grouping.
+ # (0|1) unqualified dev flag
+ # (a/b/rc/z) - release segment grouping
+ # pre-release level
+ # dev count, maxsize for releases.
+ rc_lookup = {'a': 'a', 'b': 'b', 'rc': 'rc', None: 'z'}
+ if self._dev_count and not self._prerelease_type:
+ uq_dev = 0
+ else:
+ uq_dev = 1
+ return (
+ self._major, self._minor, self._patch,
+ uq_dev,
+ rc_lookup[self._prerelease_type], self._prerelease,
+ self._dev_count or sys.maxsize)
+
+ def __lt__(self, other):
+ """Compare self and other, another Semantic Version."""
+ # NB(lifeless) this could perhaps be rewritten as
+ # lt (tuple_of_one, tuple_of_other) with a single check for
+ # the typeerror corner cases - that would likely be faster
+ # if this ever becomes performance sensitive.
+ if not isinstance(other, SemanticVersion):
+ raise TypeError("ordering to non-SemanticVersion is undefined")
+ return self._sort_key() < other._sort_key()
+
+ def __le__(self, other):
+ return self == other or self < other
+
+ def __ge__(self, other):
+ return not self < other
+
+ def __gt__(self, other):
+ return not self <= other
+
+ def __ne__(self, other):
+ return not self == other
+
+ def __repr__(self):
+ return "pbr.version.SemanticVersion(%s)" % self.release_string()
+
+ @classmethod
+ def from_pip_string(klass, version_string):
+ """Create a SemanticVersion from a pip version string.
+
+ This method will parse a version like 1.3.0 into a SemanticVersion.
+
+ This method is responsible for accepting any version string that any
+ older version of pbr ever created.
+
+ Therefore: versions like 1.3.0a1 versions are handled, parsed into a
+ canonical form and then output - resulting in 1.3.0.0a1.
+ Pre pbr-semver dev versions like 0.10.1.3.g83bef74 will be parsed but
+ output as 0.10.1.dev3.g83bef74.
+
+ :raises ValueError: Never tagged versions sdisted by old pbr result in
+ just the git hash, e.g. '1234567' which poses a substantial problem
+ since they collide with the semver versions when all the digits are
+ numerals. Such versions will result in a ValueError being thrown if
+ any non-numeric digits are present. They are an exception to the
+ general case of accepting anything we ever output, since they were
+ never intended and would permanently mess up versions on PyPI if
+ ever released - we're treating that as a critical bug that we ever
+ made them and have stopped doing that.
+ """
+
+ try:
+ return klass._from_pip_string_unsafe(version_string)
+ except IndexError:
+ raise ValueError("Invalid version %r" % version_string)
+
+ @classmethod
+ def _from_pip_string_unsafe(klass, version_string):
+ # Versions need to start numerically, ignore if not
+ if not version_string[:1].isdigit():
+ raise ValueError("Invalid version %r" % version_string)
+ input_components = version_string.split('.')
+ # decimals first (keep pre-release and dev/hashes to the right)
+ components = [c for c in input_components if c.isdigit()]
+ digit_len = len(components)
+ if digit_len == 0:
+ raise ValueError("Invalid version %r" % version_string)
+ elif digit_len < 3:
+ if (digit_len < len(input_components) and
+ input_components[digit_len][0].isdigit()):
+ # Handle X.YaZ - Y is a digit not a leadin to pre-release.
+ mixed_component = input_components[digit_len]
+ last_component = ''.join(itertools.takewhile(
+ lambda x: x.isdigit(), mixed_component))
+ components.append(last_component)
+ input_components[digit_len:digit_len + 1] = [
+ last_component, mixed_component[len(last_component):]]
+ digit_len += 1
+ components.extend([0] * (3 - digit_len))
+ components.extend(input_components[digit_len:])
+ major = int(components[0])
+ minor = int(components[1])
+ dev_count = None
+ post_count = None
+ prerelease_type = None
+ prerelease = None
+
+ def _parse_type(segment):
+ # Discard leading digits (the 0 in 0a1)
+ isdigit = operator.methodcaller('isdigit')
+ segment = ''.join(itertools.dropwhile(isdigit, segment))
+ isalpha = operator.methodcaller('isalpha')
+ prerelease_type = ''.join(itertools.takewhile(isalpha, segment))
+ prerelease = segment[len(prerelease_type)::]
+ return prerelease_type, int(prerelease)
+ if _is_int(components[2]):
+ patch = int(components[2])
+ else:
+ # legacy version e.g. 1.2.0a1 (canonical is 1.2.0.0a1)
+ # or 1.2.dev4.g1234 or 1.2.b4
+ patch = 0
+ components[2:2] = [0]
+ remainder = components[3:]
+ remainder_starts_with_int = False
+ try:
+ if remainder and int(remainder[0]):
+ remainder_starts_with_int = True
+ except ValueError:
+ pass
+ if remainder_starts_with_int:
+ # old dev format - 0.1.2.3.g1234
+ dev_count = int(remainder[0])
+ else:
+ if remainder and (remainder[0][0] == '0' or
+ remainder[0][0] in ('a', 'b', 'r')):
+ # Current RC/beta layout
+ prerelease_type, prerelease = _parse_type(remainder[0])
+ remainder = remainder[1:]
+ while remainder:
+ component = remainder[0]
+ if component.startswith('dev'):
+ dev_count = int(component[3:])
+ elif component.startswith('post'):
+ dev_count = None
+ post_count = int(component[4:])
+ else:
+ raise ValueError(
+ 'Unknown remainder %r in %r'
+ % (remainder, version_string))
+ remainder = remainder[1:]
+ result = SemanticVersion(
+ major, minor, patch, prerelease_type=prerelease_type,
+ prerelease=prerelease, dev_count=dev_count)
+ if post_count:
+ if dev_count:
+ raise ValueError(
+ 'Cannot combine postN and devN - no mapping in %r'
+ % (version_string,))
+ result = result.increment().to_dev(post_count)
+ return result
+
+ def brief_string(self):
+ """Return the short version minus any alpha/beta tags."""
+ return "%s.%s.%s" % (self._major, self._minor, self._patch)
+
+ def debian_string(self):
+ """Return the version number to use when building a debian package.
+
+ This translates the PEP440/semver precedence rules into Debian version
+ sorting operators.
+ """
+ return self._long_version("~")
+
+ def decrement(self):
+ """Return a decremented SemanticVersion.
+
+ Decrementing versions doesn't make a lot of sense - this method only
+ exists to support rendering of pre-release versions strings into
+ serialisations (such as rpm) with no sort-before operator.
+
+ The 9999 magic version component is from the spec on this - pbr-semver.
+
+ :return: A new SemanticVersion object.
+ """
+ if self._patch:
+ new_patch = self._patch - 1
+ new_minor = self._minor
+ new_major = self._major
+ else:
+ new_patch = 9999
+ if self._minor:
+ new_minor = self._minor - 1
+ new_major = self._major
+ else:
+ new_minor = 9999
+ if self._major:
+ new_major = self._major - 1
+ else:
+ new_major = 0
+ return SemanticVersion(
+ new_major, new_minor, new_patch)
+
+ def increment(self, minor=False, major=False):
+ """Return an incremented SemanticVersion.
+
+ The default behaviour is to perform a patch level increment. When
+ incrementing a prerelease version, the patch level is not changed
+ - the prerelease serial is changed (e.g. beta 0 -> beta 1).
+
+ Incrementing non-pre-release versions will not introduce pre-release
+ versions - except when doing a patch incremental to a pre-release
+ version the new version will only consist of major/minor/patch.
+
+ :param minor: Increment the minor version.
+ :param major: Increment the major version.
+ :return: A new SemanticVersion object.
+ """
+ if self._prerelease_type:
+ new_prerelease_type = self._prerelease_type
+ new_prerelease = self._prerelease + 1
+ new_patch = self._patch
+ else:
+ new_prerelease_type = None
+ new_prerelease = None
+ new_patch = self._patch + 1
+ if minor:
+ new_minor = self._minor + 1
+ new_patch = 0
+ new_prerelease_type = None
+ new_prerelease = None
+ else:
+ new_minor = self._minor
+ if major:
+ new_major = self._major + 1
+ new_minor = 0
+ new_patch = 0
+ new_prerelease_type = None
+ new_prerelease = None
+ else:
+ new_major = self._major
+ return SemanticVersion(
+ new_major, new_minor, new_patch,
+ new_prerelease_type, new_prerelease)
+
+ def _long_version(self, pre_separator, rc_marker=""):
+ """Construct a long string version of this semver.
+
+ :param pre_separator: What separator to use between components
+ that sort before rather than after. If None, use . and lower the
+ version number of the component to preserve sorting. (Used for
+ rpm support)
+ """
+ if ((self._prerelease_type or self._dev_count)
+ and pre_separator is None):
+ segments = [self.decrement().brief_string()]
+ pre_separator = "."
+ else:
+ segments = [self.brief_string()]
+ if self._prerelease_type:
+ segments.append(
+ "%s%s%s%s" % (pre_separator, rc_marker, self._prerelease_type,
+ self._prerelease))
+ if self._dev_count:
+ if not self._prerelease_type:
+ segments.append(pre_separator)
+ else:
+ segments.append('.')
+ segments.append('dev')
+ segments.append(self._dev_count)
+ return "".join(str(s) for s in segments)
+
+ def release_string(self):
+ """Return the full version of the package.
+
+ This including suffixes indicating VCS status.
+ """
+ return self._long_version(".", "0")
+
+ def rpm_string(self):
+ """Return the version number to use when building an RPM package.
+
+ This translates the PEP440/semver precedence rules into RPM version
+ sorting operators. Because RPM has no sort-before operator (such as the
+ ~ operator in dpkg), we show all prerelease versions as being versions
+ of the release before.
+ """
+ return self._long_version(None)
+
+ def to_dev(self, dev_count):
+ """Return a development version of this semver.
+
+ :param dev_count: The number of commits since the last release.
+ """
+ return SemanticVersion(
+ self._major, self._minor, self._patch, self._prerelease_type,
+ self._prerelease, dev_count=dev_count)
+
+ def version_tuple(self):
+ """Present the version as a version_info tuple.
+
+ For documentation on version_info tuples see the Python
+ documentation for sys.version_info.
+
+ Since semver and PEP-440 represent overlapping but not subsets of
+ versions, we have to have some heuristic / mapping rules, and have
+ extended the releaselevel field to have alphadev, betadev and
+ candidatedev values. When they are present the dev count is used
+ to provide the serial.
+ - a/b/rc take precedence.
+ - if there is no pre-release version the dev version is used.
+ - serial is taken from the dev/a/b/c component.
+ - final non-dev versions never get serials.
+ """
+ segments = [self._major, self._minor, self._patch]
+ if self._prerelease_type:
+ type_map = {('a', False): 'alpha',
+ ('b', False): 'beta',
+ ('rc', False): 'candidate',
+ ('a', True): 'alphadev',
+ ('b', True): 'betadev',
+ ('rc', True): 'candidatedev',
+ }
+ segments.append(
+ type_map[(self._prerelease_type, bool(self._dev_count))])
+ segments.append(self._dev_count or self._prerelease)
+ elif self._dev_count:
+ segments.append('dev')
+ segments.append(self._dev_count - 1)
+ else:
+ segments.append('final')
+ segments.append(0)
+ return tuple(segments)
+
+
+class VersionInfo(object):
+
+ def __init__(self, package):
+ """Object that understands versioning for a package
+
+ :param package: name of the python package, such as glance, or
+ python-glanceclient
+ """
+ self.package = package
+ self.version = None
+ self._cached_version = None
+ self._semantic = None
+
+ def __str__(self):
+ """Make the VersionInfo object behave like a string."""
+ return self.version_string()
+
+ def __repr__(self):
+ """Include the name."""
+ return "pbr.version.VersionInfo(%s:%s)" % (
+ self.package, self.version_string())
+
+ def _get_version_from_pkg_resources(self):
+ """Obtain a version from pkg_resources or setup-time logic if missing.
+
+ This will try to get the version of the package from the pkg_resources
+ record associated with the package, and if there is no such record
+ falls back to the logic sdist would use.
+ """
+ try:
+ requirement = pkg_resources.Requirement.parse(self.package)
+ provider = pkg_resources.get_provider(requirement)
+ result_string = provider.version
+ except pkg_resources.DistributionNotFound:
+ # The most likely cause for this is running tests in a tree
+ # produced from a tarball where the package itself has not been
+ # installed into anything. Revert to setup-time logic.
+ from pbr import packaging
+ result_string = packaging.get_version(self.package)
+ return SemanticVersion.from_pip_string(result_string)
+
+ def release_string(self):
+ """Return the full version of the package.
+
+ This including suffixes indicating VCS status.
+ """
+ return self.semantic_version().release_string()
+
+ def semantic_version(self):
+ """Return the SemanticVersion object for this version."""
+ if self._semantic is None:
+ self._semantic = self._get_version_from_pkg_resources()
+ return self._semantic
+
+ def version_string(self):
+ """Return the short version minus any alpha/beta tags."""
+ return self.semantic_version().brief_string()
+
+ # Compatibility functions
+ canonical_version_string = version_string
+ version_string_with_vcs = release_string
+
+ def cached_version_string(self, prefix=""):
+ """Return a cached version string.
+
+ This will return a cached version string if one is already cached,
+ irrespective of prefix. If none is cached, one will be created with
+ prefix and then cached and returned.
+ """
+ if not self._cached_version:
+ self._cached_version = "%s%s" % (prefix,
+ self.version_string())
+ return self._cached_version
--- /dev/null
+[metadata]
+name = pbr
+author = OpenStack
+author-email = openstack-dev@lists.openstack.org
+summary = Python Build Reasonableness
+description-file =
+ README.rst
+home-page = http://docs.openstack.org/developer/pbr/
+requires-python = >=2.6
+classifier =
+ Development Status :: 5 - Production/Stable
+ Environment :: Console
+ Environment :: OpenStack
+ Intended Audience :: Developers
+ Intended Audience :: Information Technology
+ License :: OSI Approved :: Apache Software License
+ Operating System :: OS Independent
+ Programming Language :: Python
+ Programming Language :: Python :: 2
+ Programming Language :: Python :: 2.6
+ Programming Language :: Python :: 2.7
+ Programming Language :: Python :: 3
+ Programming Language :: Python :: 3.3
+ Programming Language :: Python :: 3.4
+ Programming Language :: Python :: 3.5
+
+[files]
+packages =
+ pbr
+
+[global]
+setup-hooks =
+ pbr.hooks.setup_hook
+
+[pbr]
+autodoc_tree_index_modules = True
+autodoc_tree_excludes =
+ setup.py
+ pbr/tests/
+
+[entry_points]
+distutils.setup_keywords =
+ pbr = pbr.core:pbr
+distutils.commands =
+ testr = pbr.testr_command:Testr
+egg_info.writers =
+ pbr.json = pbr.pbr_json:write_pbr_json
+console_scripts =
+ pbr = pbr.cmd.main:main
+
+[build_sphinx]
+all-files = 1
+build-dir = doc/build
+source-dir = doc/source
+
+[wheel]
+universal = 1
+
+[egg_info]
+tag_build =
+tag_date = 0
+
--- /dev/null
+#!/usr/bin/env python
+# Copyright (c) 2013 Hewlett-Packard Development Company, L.P.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+# implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+import setuptools
+
+from pbr import util
+
+setuptools.setup(
+ **util.cfg_to_args())
--- /dev/null
+# The order of packages is significant, because pip processes them in the order
+# of appearance. Changing the order has an impact on the overall integration
+# process, which may cause wedges in the gate later.
+coverage>=4.0 # Apache-2.0
+fixtures>=3.0.0 # Apache-2.0/BSD
+hacking<0.11,>=0.10.0
+mock>=2.0 # BSD
+python-subunit>=0.0.18 # Apache-2.0/BSD
+sphinx>=1.5.1 # BSD
+oslosphinx>=4.7.0 # Apache-2.0
+six>=1.9.0 # MIT
+testrepository>=0.0.18 # Apache-2.0/BSD
+testresources>=0.2.4 # Apache-2.0/BSD
+testscenarios>=0.4 # Apache-2.0/BSD
+testtools>=1.4.0 # MIT
+virtualenv>=13.1.0 # MIT
--- /dev/null
+#!/bin/bash -xe
+# Parameters:
+# PBR_PIP_VERSION :- if not set, run pip's latest release, if set must be a
+# valid reference file entry describing what pip to use.
+# WHEELHOUSE :- if not set, use a temporary wheelhouse, set to a specific path
+# to use an existing one.
+# PIPFLAGS :- may be set to any pip global option for e.g. debugging.
+# Bootstrappping the mkenv needs to install *a* pip
+export PIPVERSION=pip
+PIPFLAGS=${PIPFLAGS:-}
+
+function mkvenv {
+ venv=$1
+
+ rm -rf $venv
+ virtualenv $venv
+ $venv/bin/pip install $PIPFLAGS -U $PIPVERSION wheel
+
+ # If a change to PBR is being tested, preinstall the wheel for it
+ if [ -n "$PBR_CHANGE" ] ; then
+ $venv/bin/pip install $PIPFLAGS $pbrsdistdir/dist/pbr-*.whl
+ fi
+}
+
+# BASE should be a directory with a subdir called "new" and in that
+# dir, there should be a git repository for every entry in PROJECTS
+BASE=${BASE:-/opt/stack}
+
+REPODIR=${REPODIR:-$BASE/new}
+
+# TODO: Figure out how to get this on to the box properly
+sudo apt-get update
+sudo apt-get install -y --force-yes libvirt-dev libxml2-dev libxslt-dev libmysqlclient-dev libpq-dev libnspr4-dev pkg-config libsqlite3-dev libzmq-dev libffi-dev libldap2-dev libsasl2-dev ccache libkrb5-dev liberasurecode-dev libjpeg-dev
+
+# FOR numpy / pyyaml
+# The source list has been removed from our apt config so rather than
+# installing deps via apt-get build-dep <PKG> we install the lists provied
+# by apt-cache showsrc <PKG>
+
+# Numpy
+sudo apt-get install -y --force-yes cython debhelper gfortran libblas-dev liblapack-dev python-all-dbg python-all-dev python-nose python-tz python3-all-dbg python3-all-dev python3-nose python3-tz
+#pyyaml
+sudo apt-get install -y --force-yes debhelper python-all-dev python-all-dbg python3-all-dev python3-all-dbg libyaml-dev cython cython-dbg quilt
+
+# And use ccache explitly
+export PATH=/usr/lib/ccache:$PATH
+
+tmpdir=$(mktemp -d)
+
+# Set up a wheelhouse
+export WHEELHOUSE=${WHEELHOUSE:-$tmpdir/.wheelhouse}
+mkvenv $tmpdir/wheelhouse
+# Specific PIP version - must succeed to be useful.
+# - build/download a local wheel so we don't hit the network on each venv.
+if [ -n "${PBR_PIP_VERSION:-}" ]; then
+ td=$(mktemp -d)
+ $tmpdir/wheelhouse/bin/pip wheel -w $td $PBR_PIP_VERSION
+ # This version will now be installed in every new venv.
+ export PIPVERSION="$td/$(ls $td)"
+ $tmpdir/wheelhouse/bin/pip install -U $PIPVERSION
+ # We have pip in global-requirements as open-ended requirements,
+ # but since we don't use -U in any other invocations, our version
+ # of pip should be sticky.
+fi
+# Build wheels for everything so we don't hit the network on each venv.
+# Not all packages properly build wheels (httpretty for example).
+# Do our best but ignore errors when making wheels.
+set +e
+$tmpdir/wheelhouse/bin/pip $PIPFLAGS wheel -w $WHEELHOUSE -f $WHEELHOUSE -r \
+ $REPODIR/requirements/global-requirements.txt
+set -e
+
+#BRANCH
+BRANCH=${OVERRIDE_ZUUL_BRANCH=:-master}
+# PROJECTS is a list of projects that we're testing
+PROJECTS=$*
+
+pbrsdistdir=$tmpdir/pbrsdist
+git clone $REPODIR/pbr $pbrsdistdir
+cd $pbrsdistdir
+
+# Prepare a wheel and flag whether a change to PBR is being tested
+if git fetch $ZUUL_URL/$ZUUL_PROJECT $ZUUL_REF ; then
+ mkvenv wheel
+ wheel/bin/python setup.py bdist_wheel
+ PBR_CHANGE=1
+fi
+
+eptest=$tmpdir/eptest
+mkdir $eptest
+cd $eptest
+
+cat <<EOF > setup.cfg
+[metadata]
+name = test_project
+
+[entry_points]
+console_scripts =
+ test_cmd = test_project:main
+
+[global]
+setup-hooks =
+ pbr.hooks.setup_hook
+EOF
+
+cat <<EOF > setup.py
+import setuptools
+
+try:
+ from requests import Timeout
+except ImportError:
+ from pip._vendor.requests import Timeout
+
+from socket import error as SocketError
+
+# Some environments have network issues that drop connections to pypi
+# when running integration tests, so we retry here so that hour-long
+# test runs are less likely to fail randomly.
+try:
+ setuptools.setup(
+ setup_requires=['pbr'],
+ pbr=True)
+except (SocketError, Timeout):
+ setuptools.setup(
+ setup_requires=['pbr'],
+ pbr=True)
+
+EOF
+
+mkdir test_project
+cat <<EOF > test_project/__init__.py
+def main():
+ print "Test cmd"
+EOF
+
+epvenv=$eptest/venv
+mkvenv $epvenv
+
+eppbrdir=$tmpdir/eppbrdir
+git clone $REPODIR/pbr $eppbrdir
+$epvenv/bin/pip $PIPFLAGS install -f $WHEELHOUSE -e $eppbrdir
+
+# First check develop
+PBR_VERSION=0.0 $epvenv/bin/python setup.py develop
+cat $epvenv/bin/test_cmd
+grep 'PBR Generated' $epvenv/bin/test_cmd
+PBR_VERSION=0.0 $epvenv/bin/python setup.py develop --uninstall
+
+# Now check install
+PBR_VERSION=0.0 $epvenv/bin/python setup.py install
+cat $epvenv/bin/test_cmd
+grep 'PBR Generated' $epvenv/bin/test_cmd
+$epvenv/bin/test_cmd | grep 'Test cmd'
+
+projectdir=$tmpdir/projects
+mkdir -p $projectdir
+sudo chown -R $USER $REPODIR
+
+export PBR_INTEGRATION=1
+export PIPFLAGS
+export PIPVERSION
+PBRVERSION=pbr
+if [ -n "$PBR_CHANGE" ] ; then
+ PBRVERSION=$(ls $pbrsdistdir/dist/pbr-*.whl)
+fi
+export PBRVERSION
+export PROJECTS
+export REPODIR
+export WHEELHOUSE
+export OS_TEST_TIMEOUT=600
+cd $REPODIR/pbr
+tox -epy27 --notest
+.tox/py27/bin/python -m pip install ${REPODIR}/requirements
+tox -epy27 -- test_integration
--- /dev/null
+#!/usr/bin/env bash
+
+# Client constraint file contains this client version pin that is in conflict
+# with installing the client from source. We should remove the version pin in
+# the constraints file before applying it for from-source installation.
+
+CONSTRAINTS_FILE="$1"
+shift 1
+
+set -e
+
+# NOTE(tonyb): Place this in the tox enviroment's log dir so it will get
+# published to logs.openstack.org for easy debugging.
+localfile="$VIRTUAL_ENV/log/upper-constraints.txt"
+
+if [[ "$CONSTRAINTS_FILE" != http* ]]; then
+ CONSTRAINTS_FILE="file://$CONSTRAINTS_FILE"
+fi
+# NOTE(tonyb): need to add curl to bindep.txt if the project supports bindep
+curl "$CONSTRAINTS_FILE" --insecure --progress-bar --output "$localfile"
+
+pip install -c"$localfile" openstack-requirements
+
+# This is the main purpose of the script: Allow local installation of
+# the current repo. It is listed in constraints file and thus any
+# install will be constrained and we need to unconstrain it.
+edit-constraints "$localfile" -- "$CLIENT_NAME"
+
+pip install -c"$localfile" -U "$@"
+exit $?
--- /dev/null
+[tox]
+minversion = 2.0
+envlist = py33,py34,py35,py26,py27,pypy,pep8,docs
+
+[testenv]
+usedevelop = True
+install_command = {toxinidir}/tools/tox_install.sh {env:UPPER_CONSTRAINTS_FILE:https://git.openstack.org/cgit/openstack/requirements/plain/upper-constraints.txt} {opts} {packages}
+passenv = PBR_INTEGRATION PIPFLAGS PIPVERSION PBRVERSION REPODIR WHEELHOUSE PROJECTS OS_TEST_TIMEOUT OS_STDOUT_CAPTURE OS_STDERR_CAPTURE
+setenv =
+ VIRTUAL_ENV={envdir}
+ CLIENT_NAME=pbr
+deps = -r{toxinidir}/test-requirements.txt
+commands =
+ python setup.py testr --testr-args='{posargs}'
+
+[tox:jenkins]
+sitepackages = True
+
+[testenv:pep8]
+commands = flake8 {posargs}
+
+[testenv:docs]
+commands = python setup.py build_sphinx
+
+[testenv:cover]
+commands =
+ python setup.py testr --coverage
+
+[testenv:venv]
+commands = {posargs}
+
+[flake8]
+# H405 multi line docstring summary not separated with an empty line
+# H904 "Wrap lines in parentheses and not a backslash for line continuation
+# Removed in current hacking (https://review.openstack.org/#/c/101701/).
+ignore = H803,H904
+exclude = .venv,.tox,dist,doc,*.egg,build
+show-source = true