├── .coveragerc ├── .gitignore ├── .gitreview ├── .mailmap ├── .pre-commit-config.yaml ├── .stestr.conf ├── .zuul.yaml ├── CONTRIBUTING.rst ├── LICENSE ├── README.rst ├── doc ├── requirements.txt └── source │ ├── conf.py │ ├── contributor │ └── index.rst │ ├── index.rst │ ├── reference │ └── index.rst │ └── user │ ├── compatibility.rst │ ├── features.rst │ ├── history.rst │ ├── index.rst │ ├── packagers.rst │ ├── releasenotes.rst │ ├── semver.rst │ └── using.rst ├── pbr ├── __init__.py ├── build.py ├── cmd │ ├── __init__.py │ └── main.py ├── core.py ├── extra_files.py ├── find_package.py ├── git.py ├── hooks │ ├── __init__.py │ ├── backwards.py │ ├── base.py │ ├── commands.py │ ├── files.py │ └── metadata.py ├── options.py ├── packaging.py ├── pbr_json.py ├── sphinxext.py ├── testr_command.py ├── tests │ ├── __init__.py │ ├── base.py │ ├── test_commands.py │ ├── test_core.py │ ├── test_files.py │ ├── test_hooks.py │ ├── test_integration.py │ ├── test_packaging.py │ ├── test_pbr_json.py │ ├── test_setup.py │ ├── test_util.py │ ├── test_version.py │ ├── test_wsgi.py │ ├── testpackage │ │ ├── CHANGES.txt │ │ ├── LICENSE.txt │ │ ├── MANIFEST.in │ │ ├── README.txt │ │ ├── data_files │ │ │ ├── a.txt │ │ │ ├── b.txt │ │ │ └── c.rst │ │ ├── doc │ │ │ └── source │ │ │ │ ├── conf.py │ │ │ │ ├── index.rst │ │ │ │ ├── installation.rst │ │ │ │ └── usage.rst │ │ ├── extra-file.txt │ │ ├── git-extra-file.txt │ │ ├── pbr_testpackage │ │ │ ├── __init__.py │ │ │ ├── _setup_hooks.py │ │ │ ├── cmd.py │ │ │ ├── extra.py │ │ │ ├── package_data │ │ │ │ ├── 1.txt │ │ │ │ └── 2.txt │ │ │ └── wsgi.py │ │ ├── setup.cfg │ │ ├── setup.py │ │ ├── src │ │ │ └── testext.c │ │ └── test-requirements.txt │ └── util.py ├── util.py └── version.py ├── playbooks └── pbr-installation-openstack │ ├── pre.yaml │ └── run.yaml ├── pyproject.toml ├── releasenotes ├── notes │ ├── bdist_wininst-removal-4a1c7c3a9f08238d.yaml │ ├── build_sphinx_removal-de990a5c14a9e64d.yaml │ ├── cmd-e6664dcbd42d3935.yaml │ ├── deprecate-pyN-requirements-364655c38fa5b780.yaml │ ├── deprecate-testr-nose-integration-56e3e11248d946fc.yaml │ ├── fix-global-replace-of-src-prefix-in-glob-eb850b94ca96993e.yaml │ ├── fix-handling-of-spaces-in-data-files-glob-0fe0c398d70dfea8.yaml │ ├── fix-keywords-as-cfg-list-6cadc5141429d7f5.yaml │ ├── fix-mapping-value-explode-with-equal-sign-41bf822fa4dd0e68.yaml │ ├── fix-pep517-metadata-regression-bc287e60e45b2732.yaml │ ├── fix-symbols-leading-spaces-f68928d75a8f0997.yaml │ ├── ignore-find-links-07cf54f465aa33a6.yaml │ ├── long-descr-content-type-f9a1003acbb8740f.yaml │ ├── pep517-support-89189ce0bab15845.yaml │ ├── remove-command-hooks-907d9c2325f306ca.yaml │ ├── support-vcs-uris-with-subdirectories-20ad68b6138f72ca.yaml │ ├── use_2to3-removal-ac48bf9fbfa049b1.yaml │ └── v_version-457b38c8679c5868.yaml └── source │ ├── conf.py │ ├── index.rst │ └── unreleased.rst ├── requirements.txt ├── setup.cfg ├── setup.py ├── test-requirements.txt ├── tools └── integration.sh └── tox.ini /.coveragerc: -------------------------------------------------------------------------------- 1 | [run] 2 | branch = True 3 | source = pbr 4 | omit = pbr/tests/* 5 | 6 | [report] 7 | ignore_errors = True 8 | precision = 2 9 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled files 2 | *.py[co] 3 | *.a 4 | *.o 5 | *.so 6 | 7 | # Sphinx 8 | doc/source/reference/api/ 9 | 10 | # Files created by releasenotes build 11 | releasenotes/build 12 | releasenotes/notes/reno.cache 13 | RELEASENOTES.rst 14 | 15 | # Packages/installer info 16 | *.egg 17 | *.egg-info 18 | dist 19 | build 20 | eggs 21 | parts 22 | bin 23 | var 24 | sdist 25 | develop-eggs 26 | .installed.cfg 27 | 28 | # Other 29 | .stestr/ 30 | .tox 31 | .venv 32 | .*.swp 33 | .coverage 34 | cover 35 | AUTHORS 36 | ChangeLog 37 | 38 | # Editor files 39 | *~ 40 | *.swp 41 | -------------------------------------------------------------------------------- /.gitreview: -------------------------------------------------------------------------------- 1 | [gerrit] 2 | host=review.opendev.org 3 | port=29418 4 | project=openstack/pbr.git 5 | -------------------------------------------------------------------------------- /.mailmap: -------------------------------------------------------------------------------- 1 | # Format is: 2 | # 3 | # 4 | Davanum Srinivas 5 | Erik M. Bray Erik Bray 6 | Zhongyue Luo 7 | -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | # We from the Oslo project decided to pin repos based on the 2 | # commit hash instead of the version tag to prevend arbitrary 3 | # code from running in developer's machines. To update to a 4 | # newer version, run `pre-commit autoupdate` and then replace 5 | # the newer versions with their commit hash. 6 | 7 | default_language_version: 8 | python: python3 9 | 10 | repos: 11 | - repo: https://github.com/pre-commit/pre-commit-hooks 12 | rev: 9136088a246768144165fcc3ecc3d31bb686920a # v3.3.0 13 | hooks: 14 | - id: trailing-whitespace 15 | # Replaces or checks mixed line ending 16 | - id: mixed-line-ending 17 | args: ['--fix', 'lf'] 18 | exclude: '.*\.(svg)$' 19 | # Forbid files which have a UTF-8 byte-order marker 20 | - id: check-byte-order-marker 21 | # Checks that non-binary executables have a proper shebang 22 | - id: check-executables-have-shebangs 23 | # Check for files that contain merge conflict strings. 24 | - id: check-merge-conflict 25 | # Check for debugger imports and py37+ breakpoint() 26 | # calls in python source 27 | - id: debug-statements 28 | - id: check-yaml 29 | files: .*\.(yaml|yml)$ 30 | - repo: local 31 | hooks: 32 | - id: flake8 33 | name: flake8 34 | additional_dependencies: 35 | - hacking>=7.0.0,<7.1.0 36 | language: python 37 | entry: flake8 38 | files: '^.*\.py$' 39 | exclude: '^(doc|releasenotes|tools)/.*$' 40 | -------------------------------------------------------------------------------- /.stestr.conf: -------------------------------------------------------------------------------- 1 | [DEFAULT] 2 | test_path=./pbr/tests 3 | top_dir=./ 4 | -------------------------------------------------------------------------------- /.zuul.yaml: -------------------------------------------------------------------------------- 1 | - job: 2 | name: pbr-installation-openstack-base 3 | timeout: 5400 4 | description: | 5 | Base job for pbr jobs that install openstack packages with current 6 | pbr. This ensures we don't break our ability to install openstack. 7 | required-projects: 8 | # TODO update this list with current active python projects 9 | - openstack/pbr 10 | - openstack/aodh 11 | - openstack/automaton 12 | - openstack/ceilometer 13 | - openstack/ceilometermiddleware 14 | - openstack/cinder 15 | - openstack/cliff 16 | - openstack/debtcollector 17 | - openstack/diskimage-builder 18 | - openstack/futurist 19 | - openstack/glance 20 | - openstack/glance_store 21 | - openstack/heat 22 | - openstack/heat-cfntools 23 | - openstack/heat-templates 24 | - openstack/horizon 25 | - openstack/ironic 26 | - openstack/ironic-lib 27 | - openstack/ironic-python-agent 28 | - openstack/keystone 29 | - openstack/keystoneauth 30 | - openstack/keystonemiddleware 31 | - openstack/manila 32 | - openstack/manila-ui 33 | - openstack/neutron 34 | - openstack/neutron-vpnaas 35 | - openstack/nova 36 | - openstack/octavia 37 | - openstack/os-apply-config 38 | - openstack/os-brick 39 | - openstack/os-client-config 40 | - openstack/os-collect-config 41 | - openstack/os-refresh-config 42 | - openstack/osc-lib 43 | - openstack/oslo.cache 44 | - openstack/oslo.concurrency 45 | - openstack/oslo.config 46 | - openstack/oslo.context 47 | - openstack/oslo.db 48 | - openstack/oslo.i18n 49 | - openstack/oslo.log 50 | - openstack/oslo.messaging 51 | - openstack/oslo.middleware 52 | - openstack/oslo.policy 53 | - openstack/oslo.reports 54 | - openstack/oslo.rootwrap 55 | - openstack/oslo.serialization 56 | - openstack/oslo.service 57 | - openstack/oslo.utils 58 | - openstack/oslo.versionedobjects 59 | - openstack/oslo.vmware 60 | - openstack/pycadf 61 | - openstack/python-cinderclient 62 | - openstack/python-glanceclient 63 | - openstack/python-heatclient 64 | - openstack/python-ironicclient 65 | - openstack/python-keystoneclient 66 | - openstack/python-manilaclient 67 | - openstack/python-neutronclient 68 | - openstack/python-novaclient 69 | - openstack/python-openstackclient 70 | - openstack/python-swiftclient 71 | - openstack/python-troveclient 72 | - openstack/python-zaqarclient 73 | - openstack/requirements 74 | - openstack/stevedore 75 | - openstack/swift 76 | - openstack/taskflow 77 | - openstack/tempest 78 | - openstack/tooz 79 | - openstack/trove 80 | - openstack/trove-dashboard 81 | - openstack/zaqar 82 | 83 | - job: 84 | name: pbr-installation-openstack 85 | parent: pbr-installation-openstack-base 86 | pre-run: playbooks/pbr-installation-openstack/pre.yaml 87 | run: playbooks/pbr-installation-openstack/run.yaml 88 | vars: 89 | pbr_pip_version: '' 90 | 91 | - job: 92 | name: pbr-installation-openstack-pip-dev 93 | description: | 94 | This job runs the pbr installations with pip trunk. 95 | parent: pbr-installation-openstack 96 | vars: 97 | pbr_pip_version: 'git+https://github.com/pypa/pip.git#egg=pip' 98 | 99 | - job: 100 | name: pbr-installation-openstack-jammy 101 | parent: pbr-installation-openstack 102 | nodeset: ubuntu-jammy 103 | 104 | - job: 105 | name: pbr-installation-openstack-pip-dev-jammy 106 | parent: pbr-installation-openstack-pip-dev 107 | nodeset: ubuntu-jammy 108 | 109 | - job: 110 | name: pbr-installation-openstack-noble 111 | parent: pbr-installation-openstack 112 | nodeset: ubuntu-noble 113 | 114 | - job: 115 | name: pbr-installation-openstack-pip-dev-noble 116 | parent: pbr-installation-openstack-pip-dev 117 | nodeset: ubuntu-noble 118 | 119 | - project: 120 | templates: 121 | - lib-forward-testing-python3 122 | - periodic-stable-jobs 123 | - publish-openstack-docs-pti 124 | check: 125 | jobs: 126 | - openstack-tox-pep8 127 | - openstack-tox-cover 128 | - openstack-tox-py27 129 | - openstack-tox-py36 130 | - openstack-tox-py37 131 | - openstack-tox-py38 132 | - openstack-tox-py39 133 | - openstack-tox-py310 134 | - openstack-tox-py311 135 | - openstack-tox-py312 136 | - pbr-installation-openstack-jammy 137 | - pbr-installation-openstack-pip-dev-jammy 138 | - pbr-installation-openstack-noble 139 | - pbr-installation-openstack-pip-dev-noble 140 | gate: 141 | jobs: 142 | - openstack-tox-pep8 143 | - openstack-tox-cover 144 | - openstack-tox-py27 145 | - openstack-tox-py36 146 | - openstack-tox-py37 147 | - openstack-tox-py38 148 | - openstack-tox-py39 149 | - openstack-tox-py310 150 | - openstack-tox-py311 151 | - openstack-tox-py312 152 | - pbr-installation-openstack-jammy 153 | - pbr-installation-openstack-pip-dev-jammy 154 | - pbr-installation-openstack-noble 155 | - pbr-installation-openstack-pip-dev-noble 156 | periodic: 157 | jobs: 158 | - pbr-installation-openstack-jammy 159 | - pbr-installation-openstack-pip-dev-jammy 160 | - pbr-installation-openstack-noble 161 | - pbr-installation-openstack-pip-dev-noble 162 | -------------------------------------------------------------------------------- /CONTRIBUTING.rst: -------------------------------------------------------------------------------- 1 | If you would like to contribute to the development of OpenStack, 2 | you must follow the steps in this page: 3 | 4 | https://docs.opendev.org/opendev/infra-manual/latest/developers.html 5 | 6 | Once those steps have been completed, changes to OpenStack 7 | should be submitted for review via the Gerrit tool, following 8 | the workflow documented at: 9 | 10 | https://docs.opendev.org/opendev/infra-manual/latest/developers.html#development-workflow 11 | 12 | Release notes are managed through the tool 13 | `reno `_. This tool will create 14 | a new file under the directory ``releasenotes`` that should 15 | be checked in with the code changes. 16 | 17 | Pull requests submitted through GitHub will be ignored. 18 | 19 | Bugs should be filed on Launchpad, not GitHub: 20 | 21 | https://bugs.launchpad.net/pbr 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | 4 | .. image:: https://img.shields.io/pypi/v/pbr.svg 5 | :target: https://pypi.python.org/pypi/pbr/ 6 | :alt: Latest Version 7 | 8 | .. image:: https://img.shields.io/pypi/dm/pbr.svg 9 | :target: https://pypi.python.org/pypi/pbr/ 10 | :alt: Downloads 11 | 12 | PBR is a library that injects some useful and sensible default behaviors 13 | into your setuptools run. It started off life as the chunks of code that 14 | were copied between all of the `OpenStack`_ projects. Around the time that 15 | OpenStack hit 18 different projects each with at least 3 active branches, 16 | it seemed like a good time to make that code into a proper reusable library. 17 | 18 | PBR is only mildly configurable. The basic idea is that there's a decent 19 | way to run things and if you do, you should reap the rewards, because then 20 | it's simple and repeatable. If you want to do things differently, cool! But 21 | you've already got the power of Python at your fingertips, so you don't 22 | really need PBR. 23 | 24 | PBR builds on top of the work that `d2to1`_ started to provide for declarative 25 | configuration. `d2to1`_ is itself an implementation of the ideas behind 26 | `distutils2`_. Although `distutils2`_ is now abandoned in favor of work towards 27 | `PEP 426`_ and Metadata 2.0, declarative config is still a great idea and 28 | specifically important in trying to distribute setup code as a library 29 | when that library itself will alter how the setup is processed. As Metadata 30 | 2.0 and other modern Python packaging PEPs come out, PBR aims to support 31 | them as quickly as possible. 32 | 33 | * License: Apache License, Version 2.0 34 | * Documentation: https://docs.openstack.org/pbr/latest/ 35 | * Source: https://opendev.org/openstack/pbr 36 | * Bugs: https://bugs.launchpad.net/pbr 37 | * Release Notes: https://docs.openstack.org/pbr/latest/user/releasenotes.html 38 | * ChangeLog: https://docs.openstack.org/pbr/latest/user/history.html 39 | 40 | .. _d2to1: https://pypi.python.org/pypi/d2to1 41 | .. _distutils2: https://pypi.python.org/pypi/Distutils2 42 | .. _PEP 426: http://legacy.python.org/dev/peps/pep-0426/ 43 | .. _OpenStack: https://www.openstack.org/ 44 | -------------------------------------------------------------------------------- /doc/requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools;python_version>='3.12' 2 | sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD 3 | sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD 4 | sphinxcontrib-apidoc>=0.2.0 # BSD 5 | openstackdocstheme>=1.18.1 # Apache-2.0 6 | reno>=2.5.0 # Apache-2.0 7 | -------------------------------------------------------------------------------- /doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (C) 2020 Red Hat, Inc. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import os 17 | import sys 18 | 19 | sys.path.insert(0, os.path.abspath('../..')) 20 | # -- General configuration ---------------------------------------------------- 21 | 22 | # Add any Sphinx extension module names here, as strings. They can be 23 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 24 | extensions = ['sphinx.ext.autodoc', 'sphinx.ext.todo', 'sphinxcontrib.apidoc'] 25 | # make openstackdocstheme optional to not increase the needed dependencies 26 | try: 27 | import openstackdocstheme 28 | extensions.append('openstackdocstheme') 29 | except ImportError: 30 | openstackdocstheme = None 31 | 32 | # openstackdocstheme options 33 | 34 | # Deprecated options for docstheme < 2.2.0, can be removed once 35 | # pbr stops supporting py27. 36 | repository_name = 'openstack/pbr' 37 | bug_project = 'pbr' 38 | bug_tag = '' 39 | # New options with openstackdocstheme >=2.2.0 40 | openstackdocs_repo_name = 'openstack/pbr' 41 | openstackdocs_auto_name = False 42 | openstackdocs_bug_project = 'pbr' 43 | openstackdocs_bug_tag = '' 44 | 45 | # autodoc generation is a bit aggressive and a nuisance when doing heavy 46 | # text edit cycles. 47 | # execute "export SPHINX_DEBUG=1" in your terminal to disable 48 | 49 | # Add any paths that contain templates here, relative to this directory. 50 | # templates_path = ['_templates'] 51 | 52 | # The suffix of source filenames. 53 | source_suffix = '.rst' 54 | 55 | # The master toctree document. 56 | master_doc = 'index' 57 | 58 | # General information about the project. 59 | project = 'pbr' 60 | copyright = '2013, OpenStack Foundation' 61 | 62 | # If true, '()' will be appended to :func: etc. cross-reference text. 63 | add_function_parentheses = True 64 | 65 | # If true, the current module name will be prepended to all description 66 | # unit titles (such as .. function::). 67 | add_module_names = True 68 | 69 | # The name of the Pygments (syntax highlighting) style to use. 70 | pygments_style = 'sphinx' 71 | 72 | exclude_trees = [] 73 | 74 | # -- Options for HTML output -------------------------------------------------- 75 | 76 | # The theme to use for HTML and HTML Help pages. Major themes that come with 77 | # Sphinx are currently 'default' and 'sphinxdoc'. 78 | if openstackdocstheme is not None: 79 | html_theme = 'openstackdocs' 80 | else: 81 | html_theme = 'default' 82 | 83 | # Output file base name for HTML help builder. 84 | htmlhelp_basename = '%sdoc' % project 85 | 86 | # Grouping the document tree into LaTeX files. List of tuples 87 | # (source start file, target name, title, author, documentclass 88 | # [howto/manual]). 89 | latex_documents = [ 90 | ('index', 91 | '%s.tex' % project, 92 | '%s Documentation' % project, 93 | 'OpenStack Foundation', 'manual'), 94 | ] 95 | 96 | # -- sphinxcontrib.apidoc configuration -------------------------------------- 97 | 98 | apidoc_module_dir = '../../pbr' 99 | apidoc_output_dir = 'reference/api' 100 | apidoc_excluded_paths = [ 101 | 'tests', 102 | ] 103 | -------------------------------------------------------------------------------- /doc/source/contributor/index.rst: -------------------------------------------------------------------------------- 1 | ============== 2 | Contributing 3 | ============== 4 | 5 | Basic Details 6 | ============= 7 | 8 | .. include:: ../../../CONTRIBUTING.rst 9 | 10 | Running the Tests for pbr 11 | ========================= 12 | 13 | The testing system is based on a combination of `tox`_ and `testr`_. The canonical 14 | approach to running tests is to simply run the command ``tox``. This will 15 | create virtual environments, populate them with dependencies and run all of 16 | the tests that OpenStack CI systems run. Behind the scenes, tox is running 17 | ``testr run --parallel``, but is set up such that you can supply any additional 18 | testr arguments that are needed to tox. For example, you can run: 19 | ``tox -- --analyze-isolation`` to cause tox to tell testr to add 20 | ``--analyze-isolation`` to its argument list. 21 | 22 | It is also possible to run the tests inside of a virtual environment 23 | you have created, or it is possible that you have all of the dependencies 24 | installed locally already. If you'd like to go this route, the requirements 25 | are listed in ``requirements.txt`` and the requirements for testing are in 26 | ``test-requirements.txt``. Installing them via pip, for instance, is simply:: 27 | 28 | pip install -r requirements.txt -r test-requirements.txt 29 | 30 | If you go this route, you can interact with the testr command directly. 31 | Running ``testr run`` will run the entire test suite. ``testr run --parallel`` 32 | will run it in parallel (this is the default incantation tox uses). More 33 | information about testr can be found at: http://wiki.openstack.org/testr 34 | 35 | .. _tox: http://tox.testrun.org/ 36 | .. _testr: https://wiki.openstack.org/wiki/Testr 37 | -------------------------------------------------------------------------------- /doc/source/index.rst: -------------------------------------------------------------------------------- 1 | ================================= 2 | pbr - Python Build Reasonableness 3 | ================================= 4 | 5 | A library for managing *setuptools* packaging needs in a consistent manner. 6 | 7 | *pbr* reads and then filters the ``setup.cfg`` data through a setup hook to 8 | fill in default values and provide more sensible behaviors, and then feeds the 9 | results in as the arguments to a call to ``setup.py`` - so the heavy lifting of 10 | handling Python packaging needs is still being done by *setuptools*. 11 | 12 | Note that we don't support the ``easy_install`` aspects of *setuptools*: while 13 | we depend on ``setup_requires``, for any ``install_requires`` we recommend that 14 | they be installed prior to running ``setup.py install`` - either by hand, or by 15 | using an install tool such as *pip*. 16 | 17 | *pbr* can and does do a bunch of things for you: 18 | 19 | * **Version**: Manage version number based on git revisions and tags 20 | * **AUTHORS**: Generate AUTHORS file from git log 21 | * **ChangeLog**: Generate ChangeLog from git log 22 | * **Manifest**: Generate a sensible manifest from git files and some standard 23 | files 24 | * **Release Notes**: Generate a release notes file using reno 25 | * **Requirements**: Store your dependencies in a pip requirements file 26 | * **long_description**: Use your README file as a long_description 27 | * **Smart find_packages**: Smartly find packages under your root package 28 | * **Sphinx Autodoc**: Generate autodoc stub files for your whole module 29 | 30 | Contents 31 | -------- 32 | 33 | .. toctree:: 34 | :maxdepth: 2 35 | 36 | user/index 37 | reference/index 38 | contributor/index 39 | -------------------------------------------------------------------------------- /doc/source/reference/index.rst: -------------------------------------------------------------------------------- 1 | =================== 2 | pbr API Reference 3 | =================== 4 | 5 | .. toctree:: 6 | 7 | api/modules 8 | -------------------------------------------------------------------------------- /doc/source/user/compatibility.rst: -------------------------------------------------------------------------------- 1 | .. 2 | The name of this document and the anchor in this document must be 3 | treated as a stable API. Links to this document are coded into 4 | pbr and deployed versions of pbr will refer users to this document 5 | in the case of certain errors. 6 | Ensure any link you use in PBR is defined via a ref with .. _name. 7 | 8 | 9 | =================== 10 | Compatibility Notes 11 | =================== 12 | 13 | Useful notes about errors users may encounter when features cannot be 14 | supported on older versions of setuptools / pip / wheel. 15 | 16 | 17 | setuptools 18 | ========== 19 | 20 | 21 | .. _evaluate-marker: 22 | 23 | evaluate_marker 24 | --------------- 25 | 26 | evaluate_markers may run into issues with the '>', '>=', '<', and '<=' 27 | operators if the installed version of setuptools is less than 17.1. Projects 28 | using these operators with markers should specify a minimum version of 17.1 29 | for setuptools. 30 | 31 | 32 | pip 33 | === 34 | 35 | markers 36 | ------- 37 | 38 | For versions of pip < 7 with pbr < 1.9, dependencies that use markers will not 39 | be installed. Projects using pbr and markers should set a minimum version of 40 | 1.9 for pbr. 41 | 42 | 43 | Recommended setup.py 44 | ==================== 45 | 46 | :ref:`setup_py`. 47 | 48 | 49 | Sphinx 50 | ====== 51 | 52 | .. _sphinx-1.5: 53 | 54 | Version 1.5.0+ 55 | -------------- 56 | 57 | The ``warning-is-error`` flag is only supported by Sphinx 1.5 and will cause 58 | errors when used with older versions. 59 | -------------------------------------------------------------------------------- /doc/source/user/features.rst: -------------------------------------------------------------------------------- 1 | ========== 2 | Features 3 | ========== 4 | 5 | To understand what *pbr* can do for you, it's probably best to look at two 6 | projects: one using pure *setuptools*, and another using *pbr*. First, let's 7 | look at the *setuptools* project. 8 | 9 | .. code-block:: none 10 | 11 | $ tree -L 1 12 | . 13 | ├── AUTHORS 14 | ├── CHANGES 15 | ├── LICENSE 16 | ├── MANIFEST.in 17 | ├── README.rst 18 | ├── requirements.txt 19 | ├── setup.cfg 20 | ├── setup.py 21 | └── somepackage 22 | 23 | $ cat setup.py 24 | setuptools.setup( 25 | name='mypackage', 26 | version='1.0.0', 27 | description='A short description', 28 | long_description="""A much longer description...""", 29 | author="John Doe", 30 | author_email='john.doe@example.com', 31 | license='BSD', 32 | ) 33 | 34 | Here's a similar package using *pbr*: 35 | 36 | .. code-block:: none 37 | 38 | $ tree -L 1 39 | . 40 | ├── LICENSE 41 | ├── README.rst 42 | ├── setup.cfg 43 | ├── setup.py 44 | └── somepackage 45 | 46 | $ cat setup.py 47 | setuptools.setup( 48 | pbr=True 49 | ) 50 | 51 | $ cat setup.cfg 52 | [metadata] 53 | name = mypackage 54 | description = A short description 55 | description_file = README.rst 56 | author = John Doe 57 | author_email = john.doe@example.com 58 | license = BSD 59 | 60 | From this, we note a couple of the main features of *pbr*: 61 | 62 | - Extensive use of ``setup.cfg`` for configuration 63 | - Automatic package metadata generation (``version``) 64 | - Automatic metadata file generation (``AUTHOR``, ``ChangeLog``, 65 | ``MANIFEST.in``, ``RELEASENOTES.txt``) 66 | 67 | In addition, there are other things that you don't see here but which *pbr* 68 | will do for you: 69 | 70 | - Helpful extensions to *setuptools* commands 71 | 72 | setup.cfg 73 | --------- 74 | 75 | .. admonition:: Summary 76 | 77 | *pbr* uses ``setup.cfg`` for all configuration, though ``setup.py`` is 78 | still required. 79 | 80 | One of the main features of *distutils2* was the use of a ``setup.cfg`` 81 | INI-style configuration file. This was used to define a package's metadata and 82 | other options that were normally supplied to the ``setup()`` function. 83 | 84 | Recent versions of `setuptools`__ have implemented some of this support, but 85 | *pbr* still allows for the definition of the following sections in 86 | ``setup.cfg``: 87 | 88 | - ``files`` 89 | - ``entry_points`` 90 | - ``backwards_compat`` 91 | 92 | For more information on these sections, refer to :doc:`/user/using`. 93 | 94 | __ https://setuptools.readthedocs.io/en/latest/setuptools.html#configuring-setup-using-setup-cfg-files 95 | 96 | Package Metadata 97 | ---------------- 98 | 99 | .. admonition:: Summary 100 | 101 | *pbr* removes the need to define a lot of configuration in either 102 | ``setup.py`` or ``setup.cfg`` by extracting this information from Git. 103 | 104 | Version 105 | ~~~~~~~ 106 | 107 | .. admonition:: Summary 108 | 109 | *pbr* will automatically configure your version for you by parsing 110 | semantically-versioned Git tags. 111 | 112 | Versions can be managed two ways - *post-versioning* and *pre-versioning*. 113 | *Post-versioning* is the default while *pre-versioning* is enabled by setting 114 | ``version`` in the ``setup.cfg`` ``metadata`` section. In both cases the actual 115 | version strings are inferred from Git. 116 | 117 | If the currently checked out revision is tagged, that tag is used as 118 | the version. 119 | 120 | If the currently checked out revision is not tagged, then we take the 121 | last tagged version number and increment it to get a minimum target 122 | version. 123 | 124 | .. note:: 125 | 126 | *pbr* supports both bare version tag (e.g. ``0.1.0``) and version prefixed 127 | with ``v`` or ``V`` (e.g. ``v0.1.0``) 128 | 129 | We then walk Git history back to the last release. Within each commit we look 130 | for a ``Sem-Ver:`` pseudo header and, if found, parse it looking for keywords. 131 | Unknown symbols are not an error (so that folk can't wedge *pbr* or break their 132 | tree), but we will emit an info-level warning message. The following symbols 133 | are recognized: 134 | 135 | - ``feature`` 136 | - ``api-break`` 137 | - ``deprecation`` 138 | - ``bugfix`` 139 | 140 | A missing ``Sem-Ver`` line is equivalent to ``Sem-Ver: bugfix``. The ``bugfix`` 141 | symbol causes a patch level increment to the version. The ``feature`` and 142 | ``deprecation`` symbols cause a minor version increment. The ``api-break`` 143 | symbol causes a major version increment. 144 | 145 | If *post-versioning* is in use, we use the resulting version number as the target 146 | version. 147 | 148 | If *pre-versioning* is in use, we check that the version set in the metadata 149 | section of ``setup.cfg`` is greater than the version we infer using the above 150 | method. If the inferred version is greater than the *pre-versioning* value we 151 | raise an error, otherwise we use the version from ``setup.cfg`` as the target. 152 | 153 | We then generate dev version strings based on the commits since the last 154 | release and include the current Git SHA to disambiguate multiple dev versions 155 | with the same number of commits since the release. 156 | 157 | .. note:: 158 | 159 | *pbr* expects Git tags to be signed for use in calculating versions. 160 | 161 | The versions are expected to be compliant with :doc:`semver`. 162 | 163 | The ``version.SemanticVersion`` class can be used to query versions of a 164 | package and present it in various forms - ``debian_version()``, 165 | ``release_string()``, ``rpm_string()``, ``version_string()``, or 166 | ``version_tuple()``. 167 | 168 | Long Description 169 | ~~~~~~~~~~~~~~~~ 170 | 171 | .. admonition:: Summary 172 | 173 | *pbr* can extract the contents of a ``README`` and use this as your long 174 | description 175 | 176 | There is no need to maintain two long descriptions and your ``README`` file is 177 | probably a good long_description. So we'll just inject the contents of your 178 | ``README.rst``, ``README.txt`` or ``README`` file into your empty 179 | ``long_description``. 180 | 181 | You can also specify the exact file you want to use using the 182 | ``description_file`` parameter. 183 | 184 | You can set the ``description_content_type`` to a MIME type that may 185 | help rendering of the description; for example ``text/markdown`` or 186 | ``text/x-rst; charset=UTF-8``. 187 | 188 | Requirements 189 | ~~~~~~~~~~~~ 190 | 191 | .. admonition:: Summary 192 | 193 | *pbr* will extract requirements from ``requirements.txt`` files and 194 | automatically populate the ``install_requires``, ``tests_require`` and 195 | ``dependency_links`` arguments to ``setup`` with them. 196 | 197 | You may not have noticed, but there are differences in how pip 198 | ``requirements.txt`` files work and how *setuptools* wants to be told about 199 | requirements. The *pip* way is nicer because it sure does make it easier to 200 | populate a *virtualenv* for testing or to just install everything you need. 201 | Duplicating the information, though, is super lame. To solve this issue, *pbr* 202 | will let you use ``requirements.txt``-format files to describe the requirements 203 | for your project and will then parse these files, split them up appropriately, 204 | and inject them into the ``install_requires``, ``tests_require`` and/or 205 | ``dependency_links`` arguments to ``setup``. Voila! 206 | 207 | Finally, it is possible to specify groups of optional dependencies, or 208 | :ref:`"extra" requirements `, in your ``setup.cfg`` rather 209 | than ``setup.py``. 210 | 211 | .. versionchanged:: 5.0 212 | 213 | Previously you could specify requirements for a given major version of 214 | Python using requirments files with a ``-pyN`` suffix. This was deprecated 215 | in 4.0 and removed in 5.0 in favour of environment markers. 216 | 217 | Automatic File Generation 218 | ------------------------- 219 | 220 | .. admonition:: Summary 221 | 222 | *pbr* can automatically generate a couple of files, which would normally 223 | have to be maintained manually, by using Git data. 224 | 225 | AUTHORS, ChangeLog 226 | ~~~~~~~~~~~~~~~~~~ 227 | 228 | .. admonition:: Summary 229 | 230 | *pbr* will automatically generate an ``AUTHORS`` and a ``ChangeLog`` file 231 | using Git logs. 232 | 233 | Why keep an ``AUTHORS`` or a ``ChangeLog`` file when Git already has all of the 234 | information you need? ``AUTHORS`` generation supports filtering/combining based 235 | on a standard ``.mailmap`` file. 236 | 237 | Manifest 238 | ~~~~~~~~ 239 | 240 | .. admonition:: Summary 241 | 242 | *pbr* will automatically generate a ``MANIFEST.in`` file based on the files 243 | Git is tracking. 244 | 245 | Just like ``AUTHORS`` and ``ChangeLog``, why keep a list of files you wish to 246 | include when you can find many of these in Git. ``MANIFEST.in`` generation 247 | ensures almost all files stored in Git, with the exception of ``.gitignore``, 248 | ``.gitreview`` and ``.pyc`` files, are automatically included in your 249 | distribution. In addition, the generated ``AUTHORS`` and ``ChangeLog`` files 250 | are also included. In many cases, this removes the need for an explicit 251 | ``MANIFEST.in`` file, though one can be provided to exclude files that are 252 | tracked via Git but which should not be included in the final release, such as 253 | test files. 254 | 255 | .. note:: 256 | 257 | ``MANIFEST.in`` files have no effect on binary distributions such as wheels. 258 | Refer to the `Python packaging tutorial`__ for more information. 259 | 260 | __ https://packaging.python.org/tutorials/distributing-packages/#manifest-in 261 | 262 | Release Notes 263 | ~~~~~~~~~~~~~ 264 | 265 | .. admonition:: Summary 266 | 267 | *pbr* will automatically use *reno* \'s ``build_reno`` setuptools command 268 | to generate a release notes file, if reno is available and configured. 269 | 270 | If using *reno*, you may wish to include a copy of the release notes in your 271 | packages. *reno* provides a ``build_reno`` `setuptools command`__ and, if reno 272 | is present and configured, *pbr* will automatically call this to generate a 273 | release notes file for inclusion in your package. 274 | 275 | __ https://docs.openstack.org/reno/latest/user/setuptools.html 276 | 277 | Setup Commands 278 | -------------- 279 | 280 | .. _build_sphinx: 281 | 282 | ``build_sphinx`` 283 | ~~~~~~~~~~~~~~~~ 284 | 285 | .. admonition:: Summary 286 | 287 | *pbr* will override the Sphinx ``build_sphinx`` command to use 288 | *pbr*-provided package metadata and automatically generate API 289 | documentation. 290 | 291 | .. deprecated:: 4.2 292 | 293 | This feature has been superseded by the `sphinxcontrib-apidoc`__ (for 294 | generation of API documentation) and :ref:`pbr.sphinxext` (for configuration 295 | of versioning via package metadata) extensions. It has been removed in 296 | version 6.0. 297 | 298 | __ https://pypi.org/project/sphinxcontrib-apidoc/ 299 | 300 | ``test`` 301 | ~~~~~~~~ 302 | 303 | .. admonition:: Summary 304 | 305 | *pbr* will automatically alias the ``test`` command to use the testing tool 306 | of your choice. 307 | 308 | .. deprecated:: 4.0 309 | 310 | *pbr* overrides the *setuptools* ``test`` command if using `testrepository`__ 311 | or `nose`__ (deprecated). 312 | 313 | - *pbr* will check for a ``.testr.conf`` file. If this exists and 314 | *testrepository* is installed, the ``test`` command will alias the *testr* 315 | test runner. If this is not the case... 316 | 317 | .. note:: 318 | 319 | This is separate to ``setup.py testr`` (note the extra ``r``) which is 320 | provided directly by the ``testrepository`` package. Be careful as there is 321 | some overlap of command arguments. 322 | 323 | - *pbr* will check if ``[nosetests]`` is defined in ``setup.cfg``. If this 324 | exists and *nose* is installed, the ``test`` command will alias the *nose* 325 | runner. If this is not the case... 326 | 327 | - In other cases no override will be installed and the ``test`` command will 328 | revert to the `setuptools default`__. 329 | 330 | __ https://testrepository.readthedocs.io/en/latest/ 331 | __ https://nose.readthedocs.io/en/latest/ 332 | __ https://setuptools.readthedocs.io/en/latest/setuptools.html#test-build-package-and-run-a-unittest-suite 333 | 334 | .. _pbr.sphinxext: 335 | 336 | Sphinx Extension 337 | ---------------- 338 | 339 | .. admonition:: Summary 340 | 341 | *pbr* provides a Sphinx extension to allow you to use *pbr* version 342 | metadata in your Sphinx documentation. 343 | 344 | .. versionadded:: 4.2 345 | 346 | *pbr* provides a Sphinx extension which can be used to configure version 347 | numbers for documentation. The package does not need to be installed for this 348 | to function. 349 | 350 | .. note:: 351 | 352 | The ``openstackdocstheme`` Sphinx theme provides similar functionality. 353 | This should be preferred for official OpenStack projects. Refer to the 354 | `documentation`__ for more information. 355 | 356 | __ https://docs.openstack.org/openstackdocstheme/ 357 | 358 | For more information on the extension, refer to :doc:`/user/using`. 359 | -------------------------------------------------------------------------------- /doc/source/user/history.rst: -------------------------------------------------------------------------------- 1 | .. include:: ../../../ChangeLog 2 | -------------------------------------------------------------------------------- /doc/source/user/index.rst: -------------------------------------------------------------------------------- 1 | =========== 2 | Using pbr 3 | =========== 4 | 5 | .. toctree:: 6 | 7 | features 8 | using 9 | packagers 10 | semver 11 | compatibility 12 | releasenotes 13 | history 14 | -------------------------------------------------------------------------------- /doc/source/user/packagers.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | Notes for Package maintainers 3 | =============================== 4 | 5 | If you are maintaining packages of software that uses *pbr*, there are some 6 | features you probably want to be aware of that can make your life easier. 7 | They are exposed by environment variables, so adding them to rules or spec 8 | files should be fairly easy. 9 | 10 | Versioning 11 | ---------- 12 | 13 | *pbr*, when run in a git repo, derives the version of a package from the 14 | git tags. When run in a tarball with a proper egg-info dir, it will happily 15 | pull the version from that. So for the most part, the package maintainers 16 | shouldn't need to care. However, if you are doing something like keeping a 17 | git repo with the sources and the packaging intermixed and it's causing pbr 18 | to get confused about whether its in its own git repo or not, you can set 19 | ``PBR_VERSION``: 20 | 21 | :: 22 | 23 | export PBR_VERSION=1.2.3 24 | 25 | and all version calculation logic will be completely skipped and the supplied 26 | version will be considered absolute. 27 | 28 | Distribution version numbers 29 | ---------------------------- 30 | 31 | *pbr* will automatically calculate upstream version numbers for *dpkg* and 32 | *rpm* using systems. Releases are easy (and obvious). When packaging 33 | pre-releases though things get more complex. Firstly, semver does not provide 34 | for any sort order between pre-releases and development snapshots, so it can be 35 | complex (perhaps intractable) to package both into one repository - we 36 | recommend with either packaging pre-release releases (alpha/beta/rc's) or dev 37 | snapshots but not both. Secondly, as pre-releases and snapshots have the same 38 | major/minor/patch version as the version they lead up to, but have to sort 39 | before it, we cannot map their version naturally into the rpm version 40 | namespace: instead we represent their versions as versions of the release 41 | before. 42 | 43 | Dependencies 44 | ------------ 45 | 46 | As of 1.0.0 *pbr* doesn't alter the dependency behaviour of *setuptools*. 47 | 48 | Older versions would invoke *pip* internally under some circumstances and 49 | required the environment variable ``SKIP_PIP_INSTALL`` to be set to prevent 50 | that. Since 1.0.0 we now document that dependencies should be installed before 51 | installing a *pbr* using package. We don't support easy install, but neither 52 | do we interfere with it today. If you observe easy install being triggered when 53 | building a binary package, then you've probably missed one or more package 54 | requirements. 55 | 56 | .. important:: 57 | 58 | We reserve the right to disable easy install via *pbr* in future, since we 59 | don't want to debug or support the interactions that can occur when using 60 | it. 61 | 62 | .. _packaging-tarballs: 63 | 64 | Tarballs 65 | -------- 66 | 67 | *pbr* includes everything in a source tarball that is in the original *git* 68 | repository. This can again cause havoc if a package maintainer is doing fancy 69 | things with combined *git* repos, and is generating a source tarball using 70 | ``python setup.py sdist`` from that repo. If that is the workflow the packager 71 | is using, setting ``SKIP_GIT_SDIST``: 72 | 73 | :: 74 | 75 | export SKIP_GIT_SDIST=1 76 | 77 | will cause all logic around using git to find the files that should be in the 78 | source tarball to be skipped. Beware though, that because *pbr* packages 79 | automatically find all of the files, most of them do not have a complete 80 | ``MANIFEST.in`` file, so its possible that a tarball produced in that way will 81 | be missing files. 82 | 83 | .. _packaging-authors-changelog: 84 | 85 | AUTHORS and ChangeLog 86 | --------------------- 87 | 88 | *pbr* generates ``AUTHORS`` and ``ChangeLog`` files from *git* information. 89 | This can cause problem in distro packaging if package maintainer is using *git* 90 | repository for packaging source. If that is the case setting 91 | ``SKIP_GENERATE_AUTHORS`` 92 | 93 | :: 94 | 95 | export SKIP_GENERATE_AUTHORS=1 96 | 97 | will cause logic around generating ``AUTHORS`` using *git* information to be 98 | skipped. Similarly setting ``SKIP_WRITE_GIT_CHANGELOG`` 99 | 100 | :: 101 | 102 | export SKIP_WRITE_GIT_CHANGELOG=1 103 | 104 | will cause logic around generating ``ChangeLog`` file using *git* 105 | information to be skipped. 106 | 107 | .. _packaging-releasenotes: 108 | 109 | Release Notes 110 | ------------- 111 | 112 | *pbr* generates a release notes file, typically called ``RELEASENOTES.rst``, 113 | if `reno`_ is present and configured. You may wish to disable this 114 | functionality. If that is the case setting ``SKIP_GENERATE_RENO`` 115 | 116 | :: 117 | 118 | export SKIP_GENERATE_RENO 119 | 120 | will disable this feature. 121 | 122 | .. _reno: https://docs.openstack.org/reno/latest/ 123 | -------------------------------------------------------------------------------- /doc/source/user/releasenotes.rst: -------------------------------------------------------------------------------- 1 | =============== 2 | Release Notes 3 | =============== 4 | 5 | .. include:: ../../../RELEASENOTES.rst 6 | :start-line: 4 7 | -------------------------------------------------------------------------------- /pbr/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/__init__.py -------------------------------------------------------------------------------- /pbr/build.py: -------------------------------------------------------------------------------- 1 | # Copyright 2021 Monty Taylor 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | """PEP-517 / PEP-660 support 16 | 17 | Add:: 18 | 19 | [build-system] 20 | requires = ["pbr>=6.0.0", "setuptools>=64.0.0"] 21 | build-backend = "pbr.build" 22 | 23 | to ``pyproject.toml`` to use this. 24 | """ 25 | 26 | from setuptools import build_meta 27 | 28 | __all__ = [ 29 | 'get_requires_for_build_sdist', 30 | 'get_requires_for_build_wheel', 31 | 'prepare_metadata_for_build_wheel', 32 | 'build_wheel', 33 | 'build_sdist', 34 | 'build_editable', 35 | 'get_requires_for_build_editable', 36 | 'prepare_metadata_for_build_editable', 37 | ] 38 | 39 | 40 | # PEP-517 41 | 42 | def get_requires_for_build_wheel(config_settings=None): 43 | return build_meta.get_requires_for_build_wheel( 44 | config_settings=config_settings, 45 | ) 46 | 47 | 48 | def get_requires_for_build_sdist(config_settings=None): 49 | return build_meta.get_requires_for_build_sdist( 50 | config_settings=config_settings, 51 | ) 52 | 53 | 54 | def prepare_metadata_for_build_wheel(metadata_directory, config_settings=None): 55 | return build_meta.prepare_metadata_for_build_wheel( 56 | metadata_directory, 57 | config_settings=config_settings, 58 | ) 59 | 60 | 61 | def build_wheel( 62 | wheel_directory, 63 | config_settings=None, 64 | metadata_directory=None, 65 | ): 66 | return build_meta.build_wheel( 67 | wheel_directory, 68 | config_settings=config_settings, 69 | metadata_directory=metadata_directory, 70 | ) 71 | 72 | 73 | def build_sdist(sdist_directory, config_settings=None): 74 | return build_meta.build_sdist( 75 | sdist_directory, 76 | config_settings=config_settings, 77 | ) 78 | 79 | 80 | # PEP-660 81 | 82 | def build_editable( 83 | wheel_directory, 84 | config_settings=None, 85 | metadata_directory=None, 86 | ): 87 | return build_meta.build_editable( 88 | wheel_directory, 89 | config_settings=config_settings, 90 | metadata_directory=metadata_directory, 91 | ) 92 | 93 | 94 | def get_requires_for_build_editable(config_settings=None): 95 | return build_meta.get_requires_for_build_editable( 96 | config_settings=config_settings, 97 | ) 98 | 99 | 100 | def prepare_metadata_for_build_editable( 101 | metadata_directory, 102 | config_settings=None, 103 | ): 104 | return build_meta.prepare_metadata_for_build_editable( 105 | metadata_directory, 106 | config_settings=config_settings, 107 | ) 108 | -------------------------------------------------------------------------------- /pbr/cmd/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/cmd/__init__.py -------------------------------------------------------------------------------- /pbr/cmd/main.py: -------------------------------------------------------------------------------- 1 | # Copyright 2014 Hewlett-Packard Development Company, L.P. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import argparse 17 | import json 18 | import sys 19 | 20 | import pkg_resources 21 | 22 | import pbr.version 23 | 24 | 25 | def _get_metadata(package_name): 26 | try: 27 | return json.loads( 28 | pkg_resources.get_distribution( 29 | package_name).get_metadata('pbr.json')) 30 | except pkg_resources.DistributionNotFound: 31 | raise Exception('Package {0} not installed'.format(package_name)) 32 | except Exception: 33 | return None 34 | 35 | 36 | def get_sha(args): 37 | sha = _get_info(args.name)['sha'] 38 | if sha: 39 | print(sha) 40 | 41 | 42 | def get_info(args): 43 | if args.short: 44 | print("{version}".format(**_get_info(args.name))) 45 | else: 46 | print("{name}\t{version}\t{released}\t{sha}".format( 47 | **_get_info(args.name))) 48 | 49 | 50 | def _get_info(name): 51 | metadata = _get_metadata(name) 52 | version = pkg_resources.get_distribution(name).version 53 | if metadata: 54 | if metadata['is_release']: 55 | released = 'released' 56 | else: 57 | released = 'pre-release' 58 | sha = metadata['git_version'] 59 | else: 60 | version_parts = version.split('.') 61 | if version_parts[-1].startswith('g'): 62 | sha = version_parts[-1][1:] 63 | released = 'pre-release' 64 | else: 65 | sha = "" 66 | released = "released" 67 | for part in version_parts: 68 | if not part.isdigit(): 69 | released = "pre-release" 70 | return dict(name=name, version=version, sha=sha, released=released) 71 | 72 | 73 | def freeze(args): 74 | sorted_dists = sorted(pkg_resources.working_set, 75 | key=lambda dist: dist.project_name.lower()) 76 | for dist in sorted_dists: 77 | info = _get_info(dist.project_name) 78 | output = "{name}=={version}".format(**info) 79 | if info['sha']: 80 | output += " # git sha {sha}".format(**info) 81 | print(output) 82 | 83 | 84 | def main(): 85 | parser = argparse.ArgumentParser( 86 | description='pbr: Python Build Reasonableness') 87 | parser.add_argument( 88 | '-v', '--version', action='version', 89 | version=str(pbr.version.VersionInfo('pbr'))) 90 | 91 | subparsers = parser.add_subparsers( 92 | title='commands', description='valid commands', help='additional help', 93 | dest='cmd') 94 | subparsers.required = True 95 | 96 | cmd_sha = subparsers.add_parser('sha', help='print sha of package') 97 | cmd_sha.set_defaults(func=get_sha) 98 | cmd_sha.add_argument('name', help='package to print sha of') 99 | 100 | cmd_info = subparsers.add_parser( 101 | 'info', help='print version info for package') 102 | cmd_info.set_defaults(func=get_info) 103 | cmd_info.add_argument('name', help='package to print info of') 104 | cmd_info.add_argument('-s', '--short', action="store_true", 105 | help='only display package version') 106 | 107 | cmd_freeze = subparsers.add_parser( 108 | 'freeze', help='print version info for all installed packages') 109 | cmd_freeze.set_defaults(func=freeze) 110 | 111 | args = parser.parse_args() 112 | try: 113 | args.func(args) 114 | except Exception as e: 115 | print(e) 116 | 117 | 118 | if __name__ == '__main__': 119 | sys.exit(main()) 120 | -------------------------------------------------------------------------------- /pbr/core.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Copyright (C) 2013 Association of Universities for Research in Astronomy 17 | # (AURA) 18 | # 19 | # Redistribution and use in source and binary forms, with or without 20 | # modification, are permitted provided that the following conditions are met: 21 | # 22 | # 1. Redistributions of source code must retain the above copyright 23 | # notice, this list of conditions and the following disclaimer. 24 | # 25 | # 2. Redistributions in binary form must reproduce the above 26 | # copyright notice, this list of conditions and the following 27 | # disclaimer in the documentation and/or other materials provided 28 | # with the distribution. 29 | # 30 | # 3. The name of AURA and its representatives may not be used to 31 | # endorse or promote products derived from this software without 32 | # specific prior written permission. 33 | # 34 | # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 35 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 36 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37 | # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 38 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 39 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 40 | # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 41 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 42 | # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 43 | # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 44 | # DAMAGE. 45 | 46 | import logging 47 | import os 48 | import sys 49 | import warnings 50 | 51 | from distutils import errors 52 | 53 | from pbr import util 54 | 55 | 56 | if sys.version_info[0] == 3: 57 | string_type = str 58 | integer_types = (int,) 59 | else: 60 | string_type = basestring # noqa 61 | integer_types = (int, long) # noqa 62 | 63 | 64 | def pbr(dist, attr, value): 65 | """Implements the actual pbr setup() keyword. 66 | 67 | When used, this should be the only keyword in your setup() aside from 68 | `setup_requires`. 69 | 70 | If given as a string, the value of pbr is assumed to be the relative path 71 | to the setup.cfg file to use. Otherwise, if it evaluates to true, it 72 | simply assumes that pbr should be used, and the default 'setup.cfg' is 73 | used. 74 | 75 | This works by reading the setup.cfg file, parsing out the supported 76 | metadata and command options, and using them to rebuild the 77 | `DistributionMetadata` object and set the newly added command options. 78 | 79 | The reason for doing things this way is that a custom `Distribution` class 80 | will not play nicely with setup_requires; however, this implementation may 81 | not work well with distributions that do use a `Distribution` subclass. 82 | """ 83 | 84 | # Distribution.finalize_options() is what calls this method. That means 85 | # there is potential for recursion here. Recursion seems to be an issue 86 | # particularly when using PEP517 build-system configs without 87 | # setup_requires in setup.py. We can avoid the recursion by setting 88 | # this canary so we don't repeat ourselves. 89 | if hasattr(dist, '_pbr_initialized'): 90 | return 91 | dist._pbr_initialized = True 92 | 93 | if not value: 94 | return 95 | if isinstance(value, string_type): 96 | path = os.path.abspath(value) 97 | else: 98 | path = os.path.abspath('setup.cfg') 99 | if not os.path.exists(path): 100 | raise errors.DistutilsFileError( 101 | 'The setup.cfg file %s does not exist.' % path) 102 | 103 | # Converts the setup.cfg file to setup() arguments 104 | try: 105 | attrs = util.cfg_to_args(path, dist.script_args) 106 | except Exception: 107 | e = sys.exc_info()[1] 108 | # NB: This will output to the console if no explicit logging has 109 | # been setup - but thats fine, this is a fatal distutils error, so 110 | # being pretty isn't the #1 goal.. being diagnosable is. 111 | logging.exception('Error parsing') 112 | raise errors.DistutilsSetupError( 113 | 'Error parsing %s: %s: %s' % (path, e.__class__.__name__, e)) 114 | 115 | # There are some metadata fields that are only supported by 116 | # setuptools and not distutils, and hence are not in 117 | # dist.metadata. We are OK to write these in. For gory details 118 | # see 119 | # https://github.com/pypa/setuptools/pull/1343 120 | _DISTUTILS_UNSUPPORTED_METADATA = ( 121 | 'long_description_content_type', 'project_urls', 'provides_extras' 122 | ) 123 | 124 | # Repeat some of the Distribution initialization code with the newly 125 | # provided attrs 126 | if attrs: 127 | # Skips 'options' and 'licence' support which are rarely used; may 128 | # add back in later if demanded 129 | for key, val in attrs.items(): 130 | if hasattr(dist.metadata, 'set_' + key): 131 | getattr(dist.metadata, 'set_' + key)(val) 132 | elif hasattr(dist.metadata, key): 133 | setattr(dist.metadata, key, val) 134 | elif hasattr(dist, key): 135 | setattr(dist, key, val) 136 | elif key in _DISTUTILS_UNSUPPORTED_METADATA: 137 | setattr(dist.metadata, key, val) 138 | else: 139 | msg = 'Unknown distribution option: %s' % repr(key) 140 | warnings.warn(msg) 141 | 142 | # Re-finalize the underlying Distribution 143 | try: 144 | super(dist.__class__, dist).finalize_options() 145 | except TypeError: 146 | # If dist is not declared as a new-style class (with object as 147 | # a subclass) then super() will not work on it. This is the case 148 | # for Python 2. In that case, fall back to doing this the ugly way 149 | dist.__class__.__bases__[-1].finalize_options(dist) 150 | 151 | # This bit comes out of distribute/setuptools 152 | if isinstance(dist.metadata.version, integer_types + (float,)): 153 | # Some people apparently take "version number" too literally :) 154 | dist.metadata.version = str(dist.metadata.version) 155 | -------------------------------------------------------------------------------- /pbr/extra_files.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | from distutils import errors 17 | import os 18 | 19 | _extra_files = [] 20 | 21 | 22 | def get_extra_files(): 23 | global _extra_files 24 | return _extra_files 25 | 26 | 27 | def set_extra_files(extra_files): 28 | # Let's do a sanity check 29 | for filename in extra_files: 30 | if not os.path.exists(filename): 31 | raise errors.DistutilsFileError( 32 | '%s from the extra_files option in setup.cfg does not ' 33 | 'exist' % filename) 34 | global _extra_files 35 | _extra_files[:] = extra_files[:] 36 | -------------------------------------------------------------------------------- /pbr/find_package.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Hewlett-Packard Development Company, L.P. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import os 17 | 18 | import setuptools 19 | 20 | 21 | def smart_find_packages(package_list): 22 | """Run find_packages the way we intend.""" 23 | packages = [] 24 | for pkg in package_list.strip().split("\n"): 25 | pkg_path = pkg.replace('.', os.path.sep) 26 | packages.append(pkg) 27 | packages.extend(['%s.%s' % (pkg, f) 28 | for f in setuptools.find_packages(pkg_path)]) 29 | return "\n".join(set(packages)) 30 | -------------------------------------------------------------------------------- /pbr/git.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 OpenStack Foundation 2 | # Copyright 2012-2013 Hewlett-Packard Development Company, L.P. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import unicode_literals 18 | 19 | import distutils.errors 20 | from distutils import log 21 | import errno 22 | import io 23 | import os 24 | import re 25 | import subprocess 26 | import time 27 | 28 | import pkg_resources 29 | 30 | from pbr import options 31 | from pbr import version 32 | 33 | 34 | def _run_shell_command(cmd, throw_on_error=False, buffer=True, env=None): 35 | if buffer: 36 | out_location = subprocess.PIPE 37 | err_location = subprocess.PIPE 38 | else: 39 | out_location = None 40 | err_location = None 41 | 42 | newenv = os.environ.copy() 43 | if env: 44 | newenv.update(env) 45 | 46 | output = subprocess.Popen(cmd, 47 | stdout=out_location, 48 | stderr=err_location, 49 | env=newenv) 50 | out = output.communicate() 51 | if output.returncode and throw_on_error: 52 | raise distutils.errors.DistutilsError( 53 | "%s returned %d" % (cmd, output.returncode)) 54 | if len(out) == 0 or not out[0] or not out[0].strip(): 55 | return '' 56 | # Since we don't control the history, and forcing users to rebase arbitrary 57 | # history to fix utf8 issues is harsh, decode with replace. 58 | return out[0].strip().decode('utf-8', 'replace') 59 | 60 | 61 | def _run_git_command(cmd, git_dir, **kwargs): 62 | if not isinstance(cmd, (list, tuple)): 63 | cmd = [cmd] 64 | return _run_shell_command( 65 | ['git', '--git-dir=%s' % git_dir] + cmd, **kwargs) 66 | 67 | 68 | def _get_git_directory(): 69 | try: 70 | return _run_shell_command(['git', 'rev-parse', '--git-dir']) 71 | except OSError as e: 72 | if e.errno == errno.ENOENT: 73 | # git not installed. 74 | return '' 75 | raise 76 | 77 | 78 | def _git_is_installed(): 79 | try: 80 | # We cannot use 'which git' as it may not be available 81 | # in some distributions, So just try 'git --version' 82 | # to see if we run into trouble 83 | _run_shell_command(['git', '--version']) 84 | except OSError: 85 | return False 86 | return True 87 | 88 | 89 | def _get_highest_tag(tags): 90 | """Find the highest tag from a list. 91 | 92 | Pass in a list of tag strings and this will return the highest 93 | (latest) as sorted by the pkg_resources version parser. 94 | """ 95 | return max(tags, key=pkg_resources.parse_version) 96 | 97 | 98 | def _find_git_files(dirname='', git_dir=None): 99 | """Behave like a file finder entrypoint plugin. 100 | 101 | We don't actually use the entrypoints system for this because it runs 102 | at absurd times. We only want to do this when we are building an sdist. 103 | """ 104 | file_list = [] 105 | if git_dir is None: 106 | git_dir = _run_git_functions() 107 | if git_dir: 108 | log.info("[pbr] In git context, generating filelist from git") 109 | file_list = _run_git_command(['ls-files', '-z'], git_dir) 110 | # Users can fix utf8 issues locally with a single commit, so we are 111 | # strict here. 112 | file_list = file_list.split(b'\x00'.decode('utf-8')) 113 | return [f for f in file_list if f] 114 | 115 | 116 | def _get_raw_tag_info(git_dir): 117 | describe = _run_git_command(['describe', '--always'], git_dir) 118 | if "-" in describe: 119 | return describe.rsplit("-", 2)[-2] 120 | if "." in describe: 121 | return 0 122 | return None 123 | 124 | 125 | def get_is_release(git_dir): 126 | return _get_raw_tag_info(git_dir) == 0 127 | 128 | 129 | def _run_git_functions(): 130 | git_dir = None 131 | if _git_is_installed(): 132 | git_dir = _get_git_directory() 133 | return git_dir or None 134 | 135 | 136 | def get_git_short_sha(git_dir=None): 137 | """Return the short sha for this repo, if it exists.""" 138 | if not git_dir: 139 | git_dir = _run_git_functions() 140 | if git_dir: 141 | return _run_git_command( 142 | ['log', '-n1', '--pretty=format:%h'], git_dir) 143 | return None 144 | 145 | 146 | def _clean_changelog_message(msg): 147 | """Cleans any instances of invalid sphinx wording. 148 | 149 | This escapes/removes any instances of invalid characters 150 | that can be interpreted by sphinx as a warning or error 151 | when translating the Changelog into an HTML file for 152 | documentation building within projects. 153 | 154 | * Escapes '_' which is interpreted as a link 155 | * Escapes '*' which is interpreted as a new line 156 | * Escapes '`' which is interpreted as a literal 157 | """ 158 | 159 | msg = msg.replace('*', r'\*') 160 | msg = msg.replace('_', r'\_') 161 | msg = msg.replace('`', r'\`') 162 | 163 | return msg 164 | 165 | 166 | def _iter_changelog(changelog): 167 | """Convert a oneline log iterator to formatted strings. 168 | 169 | :param changelog: An iterator of one line log entries like 170 | that given by _iter_log_oneline. 171 | :return: An iterator over (release, formatted changelog) tuples. 172 | """ 173 | first_line = True 174 | current_release = None 175 | yield current_release, "CHANGES\n=======\n\n" 176 | for hash, tags, msg in changelog: 177 | if tags: 178 | current_release = _get_highest_tag(tags) 179 | underline = len(current_release) * '-' 180 | if not first_line: 181 | yield current_release, '\n' 182 | yield current_release, ( 183 | "%(tag)s\n%(underline)s\n\n" % 184 | dict(tag=current_release, underline=underline)) 185 | 186 | if not msg.startswith("Merge "): 187 | if msg.endswith("."): 188 | msg = msg[:-1] 189 | msg = _clean_changelog_message(msg) 190 | yield current_release, "* %(msg)s\n" % dict(msg=msg) 191 | first_line = False 192 | 193 | 194 | def _iter_log_oneline(git_dir=None): 195 | """Iterate over --oneline log entries if possible. 196 | 197 | This parses the output into a structured form but does not apply 198 | presentation logic to the output - making it suitable for different 199 | uses. 200 | 201 | :return: An iterator of (hash, tags_set, 1st_line) tuples, or None if 202 | changelog generation is disabled / not available. 203 | """ 204 | if git_dir is None: 205 | git_dir = _get_git_directory() 206 | if not git_dir: 207 | return [] 208 | return _iter_log_inner(git_dir) 209 | 210 | 211 | def _is_valid_version(candidate): 212 | try: 213 | version.SemanticVersion.from_pip_string(candidate) 214 | return True 215 | except ValueError: 216 | return False 217 | 218 | 219 | def _iter_log_inner(git_dir): 220 | """Iterate over --oneline log entries. 221 | 222 | This parses the output intro a structured form but does not apply 223 | presentation logic to the output - making it suitable for different 224 | uses. 225 | 226 | .. caution:: this function risk to return a tag that doesn't exist really 227 | inside the git objects list due to replacement made 228 | to tag name to also list pre-release suffix. 229 | Compliant with the SemVer specification (e.g 1.2.3-rc1) 230 | 231 | :return: An iterator of (hash, tags_set, 1st_line) tuples. 232 | """ 233 | log.info('[pbr] Generating ChangeLog') 234 | log_cmd = ['log', '--decorate=full', '--format=%h%x00%s%x00%d'] 235 | changelog = _run_git_command(log_cmd, git_dir) 236 | for line in changelog.split('\n'): 237 | line_parts = line.split('\x00') 238 | if len(line_parts) != 3: 239 | continue 240 | sha, msg, refname = line_parts 241 | tags = set() 242 | 243 | # refname can be: 244 | # 245 | # HEAD, tag: refs/tags/1.4.0, refs/remotes/origin/master, \ 246 | # refs/heads/master 247 | # refs/tags/1.3.4 248 | if "refs/tags/" in refname: 249 | refname = refname.strip()[1:-1] # remove wrapping ()'s 250 | # If we start with "tag: refs/tags/1.2b1, tag: refs/tags/1.2" 251 | # The first split gives us "['', '1.2b1, tag:', '1.2']" 252 | # Which is why we do the second split below on the comma 253 | for tag_string in refname.split("refs/tags/")[1:]: 254 | # git tag does not allow : or " " in tag names, so we split 255 | # on ", " which is the separator between elements 256 | candidate = tag_string.split(", ")[0].replace("-", ".") 257 | if _is_valid_version(candidate): 258 | tags.add(candidate) 259 | 260 | yield sha, tags, msg 261 | 262 | 263 | def write_git_changelog(git_dir=None, dest_dir=os.path.curdir, 264 | option_dict=None, changelog=None): 265 | """Write a changelog based on the git changelog.""" 266 | start = time.time() 267 | if not option_dict: 268 | option_dict = {} 269 | should_skip = options.get_boolean_option(option_dict, 'skip_changelog', 270 | 'SKIP_WRITE_GIT_CHANGELOG') 271 | if should_skip: 272 | return 273 | if not changelog: 274 | changelog = _iter_log_oneline(git_dir=git_dir) 275 | if changelog: 276 | changelog = _iter_changelog(changelog) 277 | if not changelog: 278 | return 279 | 280 | new_changelog = os.path.join(dest_dir, 'ChangeLog') 281 | if os.path.exists(new_changelog) and not os.access(new_changelog, os.W_OK): 282 | # If there's already a ChangeLog and it's not writable, just use it 283 | log.info('[pbr] ChangeLog not written (file already' 284 | ' exists and it is not writeable)') 285 | return 286 | 287 | log.info('[pbr] Writing ChangeLog') 288 | with io.open(new_changelog, "w", encoding="utf-8") as changelog_file: 289 | for release, content in changelog: 290 | changelog_file.write(content) 291 | stop = time.time() 292 | log.info('[pbr] ChangeLog complete (%0.1fs)' % (stop - start)) 293 | 294 | 295 | def generate_authors(git_dir=None, dest_dir='.', option_dict=dict()): 296 | """Create AUTHORS file using git commits.""" 297 | should_skip = options.get_boolean_option(option_dict, 'skip_authors', 298 | 'SKIP_GENERATE_AUTHORS') 299 | if should_skip: 300 | return 301 | 302 | start = time.time() 303 | old_authors = os.path.join(dest_dir, 'AUTHORS.in') 304 | new_authors = os.path.join(dest_dir, 'AUTHORS') 305 | if os.path.exists(new_authors) and not os.access(new_authors, os.W_OK): 306 | # If there's already an AUTHORS file and it's not writable, just use it 307 | return 308 | 309 | log.info('[pbr] Generating AUTHORS') 310 | ignore_emails = '((jenkins|zuul)@review|infra@lists|jenkins@openstack)' 311 | if git_dir is None: 312 | git_dir = _get_git_directory() 313 | if git_dir: 314 | authors = [] 315 | 316 | # don't include jenkins email address in AUTHORS file 317 | git_log_cmd = ['log', '--format=%aN <%aE>'] 318 | authors += _run_git_command(git_log_cmd, git_dir).split('\n') 319 | authors = [a for a in authors if not re.search(ignore_emails, a)] 320 | 321 | # get all co-authors from commit messages 322 | co_authors_out = _run_git_command('log', git_dir) 323 | co_authors = re.findall('Co-authored-by:.+', co_authors_out, 324 | re.MULTILINE) 325 | co_authors = [signed.split(":", 1)[1].strip() 326 | for signed in co_authors if signed] 327 | 328 | authors += co_authors 329 | authors = sorted(set(authors)) 330 | 331 | with open(new_authors, 'wb') as new_authors_fh: 332 | if os.path.exists(old_authors): 333 | with open(old_authors, "rb") as old_authors_fh: 334 | new_authors_fh.write(old_authors_fh.read()) 335 | new_authors_fh.write(('\n'.join(authors) + '\n') 336 | .encode('utf-8')) 337 | stop = time.time() 338 | log.info('[pbr] AUTHORS complete (%0.1fs)' % (stop - start)) 339 | -------------------------------------------------------------------------------- /pbr/hooks/__init__.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Hewlett-Packard Development Company, L.P. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from pbr.hooks import backwards 17 | from pbr.hooks import commands 18 | from pbr.hooks import files 19 | from pbr.hooks import metadata 20 | 21 | 22 | def setup_hook(config): 23 | """Filter config parsed from a setup.cfg to inject our defaults.""" 24 | metadata_config = metadata.MetadataConfig(config) 25 | metadata_config.run() 26 | backwards.BackwardsCompatConfig(config).run() 27 | commands.CommandsConfig(config).run() 28 | files.FilesConfig(config, metadata_config.get_name()).run() 29 | -------------------------------------------------------------------------------- /pbr/hooks/backwards.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Hewlett-Packard Development Company, L.P. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from pbr.hooks import base 17 | from pbr import packaging 18 | 19 | 20 | class BackwardsCompatConfig(base.BaseConfig): 21 | 22 | section = 'backwards_compat' 23 | 24 | def hook(self): 25 | self.config['include_package_data'] = 'True' 26 | packaging.append_text_list( 27 | self.config, 'dependency_links', 28 | packaging.parse_dependency_links()) 29 | packaging.append_text_list( 30 | self.config, 'tests_require', 31 | packaging.parse_requirements( 32 | packaging.TEST_REQUIREMENTS_FILES, 33 | strip_markers=True)) 34 | -------------------------------------------------------------------------------- /pbr/hooks/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Hewlett-Packard Development Company, L.P. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | 17 | class BaseConfig(object): 18 | 19 | section = None 20 | 21 | def __init__(self, config): 22 | self._global_config = config 23 | self.config = self._global_config.get(self.section, dict()) 24 | self.pbr_config = config.get('pbr', dict()) 25 | 26 | def run(self): 27 | self.hook() 28 | self.save() 29 | 30 | def hook(self): 31 | pass 32 | 33 | def save(self): 34 | self._global_config[self.section] = self.config 35 | -------------------------------------------------------------------------------- /pbr/hooks/commands.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Hewlett-Packard Development Company, L.P. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import os 17 | 18 | from setuptools.command import easy_install 19 | 20 | from pbr.hooks import base 21 | from pbr import options 22 | from pbr import packaging 23 | 24 | 25 | class CommandsConfig(base.BaseConfig): 26 | 27 | section = 'global' 28 | 29 | def __init__(self, config): 30 | super(CommandsConfig, self).__init__(config) 31 | self.commands = self.config.get('commands', "") 32 | 33 | def save(self): 34 | self.config['commands'] = self.commands 35 | super(CommandsConfig, self).save() 36 | 37 | def add_command(self, command): 38 | self.commands = "%s\n%s" % (self.commands, command) 39 | 40 | def hook(self): 41 | self.add_command('pbr.packaging.LocalEggInfo') 42 | self.add_command('pbr.packaging.LocalSDist') 43 | self.add_command('pbr.packaging.LocalInstallScripts') 44 | self.add_command('pbr.packaging.LocalDevelop') 45 | self.add_command('pbr.packaging.LocalRPMVersion') 46 | self.add_command('pbr.packaging.LocalDebVersion') 47 | if os.name != 'nt': 48 | easy_install.get_script_args = packaging.override_get_script_args 49 | 50 | if os.path.exists('.testr.conf') and packaging.have_testr(): 51 | # There is a .testr.conf file. We want to use it. 52 | self.add_command('pbr.packaging.TestrTest') 53 | elif self.config.get('nosetests', False) and packaging.have_nose(): 54 | # We seem to still have nose configured 55 | self.add_command('pbr.packaging.NoseTest') 56 | 57 | use_egg = options.get_boolean_option( 58 | self.pbr_config, 'use-egg', 'PBR_USE_EGG') 59 | # We always want non-egg install unless explicitly requested 60 | if 'manpages' in self.pbr_config or not use_egg: 61 | self.add_command('pbr.packaging.LocalInstall') 62 | else: 63 | self.add_command('pbr.packaging.InstallWithGit') 64 | -------------------------------------------------------------------------------- /pbr/hooks/files.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Hewlett-Packard Development Company, L.P. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | import os 17 | import shlex 18 | import sys 19 | 20 | from pbr import find_package 21 | from pbr.hooks import base 22 | 23 | 24 | def get_manpath(): 25 | manpath = 'share/man' 26 | if os.path.exists(os.path.join(sys.prefix, 'man')): 27 | # This works around a bug with install where it expects every node 28 | # in the relative data directory to be an actual directory, since at 29 | # least Debian derivatives (and probably other platforms as well) 30 | # like to symlink Unixish /usr/local/man to /usr/local/share/man. 31 | manpath = 'man' 32 | return manpath 33 | 34 | 35 | def get_man_section(section): 36 | return os.path.join(get_manpath(), 'man%s' % section) 37 | 38 | 39 | def unquote_path(path): 40 | # unquote the full path, e.g: "'a/full/path'" becomes "a/full/path", also 41 | # strip the quotes off individual path components because os.walk cannot 42 | # handle paths like: "'i like spaces'/'another dir'", so we will pass it 43 | # "i like spaces/another dir" instead. 44 | 45 | if os.name == 'nt': 46 | # shlex cannot handle paths that contain backslashes, treating those 47 | # as escape characters. 48 | path = path.replace("\\", "/") 49 | return "".join(shlex.split(path)).replace("/", "\\") 50 | 51 | return "".join(shlex.split(path)) 52 | 53 | 54 | class FilesConfig(base.BaseConfig): 55 | 56 | section = 'files' 57 | 58 | def __init__(self, config, name): 59 | super(FilesConfig, self).__init__(config) 60 | self.name = name 61 | self.data_files = self.config.get('data_files', '') 62 | 63 | def save(self): 64 | self.config['data_files'] = self.data_files 65 | super(FilesConfig, self).save() 66 | 67 | def expand_globs(self): 68 | finished = [] 69 | for line in self.data_files.split("\n"): 70 | if line.rstrip().endswith('*') and '=' in line: 71 | (target, source_glob) = line.split('=') 72 | source_prefix = source_glob.strip()[:-1] 73 | target = target.strip() 74 | if not target.endswith(os.path.sep): 75 | target += os.path.sep 76 | unquoted_prefix = unquote_path(source_prefix) 77 | unquoted_target = unquote_path(target) 78 | for (dirpath, dirnames, fnames) in os.walk(unquoted_prefix): 79 | # As source_prefix is always matched, using replace with a 80 | # a limit of one is always going to replace the path prefix 81 | # and not accidentally replace some text in the middle of 82 | # the path 83 | new_prefix = dirpath.replace(unquoted_prefix, 84 | unquoted_target, 1) 85 | finished.append("'%s' = " % new_prefix) 86 | finished.extend( 87 | [" '%s'" % os.path.join(dirpath, f) for f in fnames]) 88 | else: 89 | finished.append(line) 90 | 91 | self.data_files = "\n".join(finished) 92 | 93 | def add_man_path(self, man_path): 94 | self.data_files = "%s\n'%s' =" % (self.data_files, man_path) 95 | 96 | def add_man_page(self, man_page): 97 | self.data_files = "%s\n '%s'" % (self.data_files, man_page) 98 | 99 | def get_man_sections(self): 100 | man_sections = dict() 101 | manpages = self.pbr_config['manpages'] 102 | for manpage in manpages.split(): 103 | section_number = manpage.strip()[-1] 104 | section = man_sections.get(section_number, list()) 105 | section.append(manpage.strip()) 106 | man_sections[section_number] = section 107 | return man_sections 108 | 109 | def hook(self): 110 | packages = self.config.get('packages', self.name).strip() 111 | expanded = [] 112 | for pkg in packages.split("\n"): 113 | if os.path.isdir(pkg.strip()): 114 | expanded.append(find_package.smart_find_packages(pkg.strip())) 115 | 116 | self.config['packages'] = "\n".join(expanded) 117 | 118 | self.expand_globs() 119 | 120 | if 'manpages' in self.pbr_config: 121 | man_sections = self.get_man_sections() 122 | for (section, pages) in man_sections.items(): 123 | manpath = get_man_section(section) 124 | self.add_man_path(manpath) 125 | for page in pages: 126 | self.add_man_page(page) 127 | -------------------------------------------------------------------------------- /pbr/hooks/metadata.py: -------------------------------------------------------------------------------- 1 | # Copyright 2013 Hewlett-Packard Development Company, L.P. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from pbr.hooks import base 17 | from pbr import packaging 18 | 19 | 20 | class MetadataConfig(base.BaseConfig): 21 | 22 | section = 'metadata' 23 | 24 | def hook(self): 25 | self.config['version'] = packaging.get_version( 26 | self.config['name'], self.config.get('version', None)) 27 | packaging.append_text_list( 28 | self.config, 'requires_dist', 29 | packaging.parse_requirements()) 30 | 31 | def get_name(self): 32 | return self.config['name'] 33 | -------------------------------------------------------------------------------- /pbr/options.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 10 | # implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | # 14 | # Copyright (C) 2013 Association of Universities for Research in Astronomy 15 | # (AURA) 16 | # 17 | # Redistribution and use in source and binary forms, with or without 18 | # modification, are permitted provided that the following conditions are met: 19 | # 20 | # 1. Redistributions of source code must retain the above copyright 21 | # notice, this list of conditions and the following disclaimer. 22 | # 23 | # 2. Redistributions in binary form must reproduce the above 24 | # copyright notice, this list of conditions and the following 25 | # disclaimer in the documentation and/or other materials provided 26 | # with the distribution. 27 | # 28 | # 3. The name of AURA and its representatives may not be used to 29 | # endorse or promote products derived from this software without 30 | # specific prior written permission. 31 | # 32 | # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 33 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 34 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 35 | # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 36 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 37 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 38 | # OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 39 | # ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 40 | # TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 41 | # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 42 | # DAMAGE. 43 | 44 | import os 45 | 46 | 47 | TRUE_VALUES = ('true', '1', 'yes') 48 | 49 | 50 | def get_boolean_option(option_dict, option_name, env_name): 51 | return ((option_name in option_dict and 52 | option_dict[option_name][1].lower() in TRUE_VALUES) or 53 | str(os.getenv(env_name)).lower() in TRUE_VALUES) 54 | -------------------------------------------------------------------------------- /pbr/pbr_json.py: -------------------------------------------------------------------------------- 1 | # Copyright 2011 OpenStack Foundation 2 | # Copyright 2012-2013 Hewlett-Packard Development Company, L.P. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | import json 18 | 19 | from pbr import git 20 | 21 | 22 | def write_pbr_json(cmd, basename, filename): 23 | if not hasattr(cmd.distribution, 'pbr') or not cmd.distribution.pbr: 24 | return 25 | git_dir = git._run_git_functions() 26 | if not git_dir: 27 | return 28 | values = dict() 29 | git_version = git.get_git_short_sha(git_dir) 30 | is_release = git.get_is_release(git_dir) 31 | if git_version is not None: 32 | values['git_version'] = git_version 33 | values['is_release'] = is_release 34 | cmd.write_file('pbr', filename, json.dumps(values, sort_keys=True)) 35 | -------------------------------------------------------------------------------- /pbr/sphinxext.py: -------------------------------------------------------------------------------- 1 | # Copyright 2018 Red Hat, Inc. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | # (hberaud) do not use six here to import configparser 17 | # to keep this module free from external dependencies 18 | # to avoid cross dependencies errors on minimal system 19 | # free from dependencies. 20 | try: 21 | import configparser 22 | except ImportError: 23 | import ConfigParser as configparser 24 | import os.path 25 | 26 | from sphinx.util import logging 27 | 28 | import pbr.version 29 | 30 | _project = None 31 | logger = logging.getLogger(__name__) 32 | 33 | 34 | def _find_setup_cfg(srcdir): 35 | """Find the 'setup.cfg' file, if it exists. 36 | 37 | This assumes we're using 'doc/source' for documentation, but also allows 38 | for single level 'doc' paths. 39 | """ 40 | # TODO(stephenfin): Are we sure that this will always exist, e.g. for 41 | # an sdist or wheel? Perhaps we should check for 'PKG-INFO' or 42 | # 'METADATA' files, a la 'pbr.packaging._get_version_from_pkg_metadata' 43 | for path in [ 44 | os.path.join(srcdir, os.pardir, 'setup.cfg'), 45 | os.path.join(srcdir, os.pardir, os.pardir, 'setup.cfg')]: 46 | if os.path.exists(path): 47 | return path 48 | 49 | return None 50 | 51 | 52 | def _get_project_name(srcdir): 53 | """Return string name of project name, or None. 54 | 55 | This extracts metadata from 'setup.cfg'. We don't rely on 56 | distutils/setuptools as we don't want to actually install the package 57 | simply to build docs. 58 | """ 59 | global _project 60 | 61 | if _project is None: 62 | parser = configparser.ConfigParser() 63 | 64 | path = _find_setup_cfg(srcdir) 65 | if not path or not parser.read(path): 66 | logger.info('Could not find a setup.cfg to extract project name ' 67 | 'from') 68 | return None 69 | 70 | try: 71 | # for project name we use the name in setup.cfg, but if the 72 | # length is longer then 32 we use summary. Otherwise thAe 73 | # menu rendering looks brolen 74 | project = parser.get('metadata', 'name') 75 | if len(project.split()) == 1 and len(project) > 32: 76 | project = parser.get('metadata', 'summary') 77 | except configparser.Error: 78 | logger.info('Could not extract project metadata from setup.cfg') 79 | return None 80 | 81 | _project = project 82 | 83 | return _project 84 | 85 | 86 | def _builder_inited(app): 87 | # TODO(stephenfin): Once Sphinx 1.8 is released, we should move the below 88 | # to a 'config-inited' handler 89 | 90 | project_name = _get_project_name(app.srcdir) 91 | try: 92 | version_info = pbr.version.VersionInfo(project_name) 93 | except Exception: 94 | version_info = None 95 | 96 | if version_info and not app.config.version and not app.config.release: 97 | app.config.version = version_info.canonical_version_string() 98 | app.config.release = version_info.version_string_with_vcs() 99 | 100 | 101 | def setup(app): 102 | app.connect('builder-inited', _builder_inited) 103 | return { 104 | 'parallel_read_safe': True, 105 | 'parallel_write_safe': True, 106 | } 107 | -------------------------------------------------------------------------------- /pbr/testr_command.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Copyright (c) 2013 Testrepository Contributors 17 | # 18 | # Licensed under either the Apache License, Version 2.0 or the BSD 3-clause 19 | # license at the users choice. A copy of both licenses are available in the 20 | # project source as Apache-2.0 and BSD. You may not use this file except in 21 | # compliance with one of these two licences. 22 | # 23 | # Unless required by applicable law or agreed to in writing, software 24 | # distributed under these licenses is distributed on an "AS IS" BASIS, WITHOUT 25 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 26 | # license you chose for the specific language governing permissions and 27 | # limitations under that license. 28 | 29 | """setuptools/distutils command to run testr via setup.py 30 | 31 | PBR will hook in the Testr class to provide "setup.py test" when 32 | .testr.conf is present in the repository (see pbr/hooks/commands.py). 33 | 34 | If we are activated but testrepository is not installed, we provide a 35 | sensible error. 36 | 37 | You can pass --coverage which will also export PYTHON='coverage run 38 | --source ' and automatically combine the coverage from 39 | each testr backend test runner after the run completes. 40 | 41 | """ 42 | 43 | from distutils import cmd 44 | import distutils.errors 45 | import logging 46 | import os 47 | import sys 48 | import warnings 49 | 50 | logger = logging.getLogger(__name__) 51 | 52 | 53 | class TestrReal(cmd.Command): 54 | 55 | description = "DEPRECATED: Run unit tests using testr" 56 | 57 | user_options = [ 58 | ('coverage', None, "Replace PYTHON with coverage and merge coverage " 59 | "from each testr worker."), 60 | ('testr-args=', 't', "Run 'testr' with these args"), 61 | ('omit=', 'o', "Files to omit from coverage calculations"), 62 | ('coverage-package-name=', None, "Use this name to select packages " 63 | "for coverage (one or more, " 64 | "comma-separated)"), 65 | ('slowest', None, "Show slowest test times after tests complete."), 66 | ('no-parallel', None, "Run testr serially"), 67 | ('log-level=', 'l', "Log level (default: info)"), 68 | ] 69 | 70 | boolean_options = ['coverage', 'slowest', 'no_parallel'] 71 | 72 | def _run_testr(self, *args): 73 | logger.debug("_run_testr called with args = %r", args) 74 | return commands.run_argv([sys.argv[0]] + list(args), 75 | sys.stdin, sys.stdout, sys.stderr) 76 | 77 | def initialize_options(self): 78 | self.testr_args = None 79 | self.coverage = None 80 | self.omit = "" 81 | self.slowest = None 82 | self.coverage_package_name = None 83 | self.no_parallel = None 84 | self.log_level = 'info' 85 | 86 | def finalize_options(self): 87 | self.log_level = getattr( 88 | logging, 89 | self.log_level.upper(), 90 | logging.INFO) 91 | logging.basicConfig(level=self.log_level) 92 | logger.debug("finalize_options called") 93 | if self.testr_args is None: 94 | self.testr_args = [] 95 | else: 96 | self.testr_args = self.testr_args.split() 97 | if self.omit: 98 | self.omit = "--omit=%s" % self.omit 99 | logger.debug("finalize_options: self.__dict__ = %r", self.__dict__) 100 | 101 | def run(self): 102 | """Set up testr repo, then run testr.""" 103 | logger.debug("run called") 104 | 105 | warnings.warn('testr integration in pbr is deprecated. Please use ' 106 | 'the \'testr\' setup command or call testr directly', 107 | DeprecationWarning) 108 | 109 | if not os.path.isdir(".testrepository"): 110 | self._run_testr("init") 111 | 112 | if self.coverage: 113 | self._coverage_before() 114 | if not self.no_parallel: 115 | testr_ret = self._run_testr("run", "--parallel", *self.testr_args) 116 | else: 117 | testr_ret = self._run_testr("run", *self.testr_args) 118 | if testr_ret: 119 | raise distutils.errors.DistutilsError( 120 | "testr failed (%d)" % testr_ret) 121 | if self.slowest: 122 | print("Slowest Tests") 123 | self._run_testr("slowest") 124 | if self.coverage: 125 | self._coverage_after() 126 | 127 | def _coverage_before(self): 128 | logger.debug("_coverage_before called") 129 | package = self.distribution.get_name() 130 | if package.startswith('python-'): 131 | package = package[7:] 132 | 133 | # Use this as coverage package name 134 | if self.coverage_package_name: 135 | package = self.coverage_package_name 136 | options = "--source %s --parallel-mode" % package 137 | os.environ['PYTHON'] = ("coverage run %s" % options) 138 | logger.debug("os.environ['PYTHON'] = %r", os.environ['PYTHON']) 139 | 140 | def _coverage_after(self): 141 | logger.debug("_coverage_after called") 142 | os.system("coverage combine") 143 | os.system("coverage html -d ./cover %s" % self.omit) 144 | os.system("coverage xml -o ./cover/coverage.xml %s" % self.omit) 145 | 146 | 147 | class TestrFake(cmd.Command): 148 | description = "Run unit tests using testr" 149 | user_options = [] 150 | 151 | def initialize_options(self): 152 | pass 153 | 154 | def finalize_options(self): 155 | pass 156 | 157 | def run(self): 158 | print("Install testrepository to run 'testr' command properly.") 159 | 160 | 161 | try: 162 | from testrepository import commands 163 | have_testr = True 164 | Testr = TestrReal 165 | except ImportError: 166 | have_testr = False 167 | Testr = TestrFake 168 | -------------------------------------------------------------------------------- /pbr/tests/__init__.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); 2 | # you may not use this file except in compliance with the License. 3 | # You may obtain a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, 9 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 10 | # implied. 11 | # See the License for the specific language governing permissions and 12 | # limitations under the License. 13 | 14 | import os 15 | 16 | import testscenarios 17 | 18 | 19 | def load_tests(loader, standard_tests, pattern): 20 | # top level directory cached on loader instance 21 | this_dir = os.path.dirname(__file__) 22 | package_tests = loader.discover(start_dir=this_dir, pattern=pattern) 23 | result = loader.suiteClass() 24 | result.addTests(testscenarios.generate_scenarios(standard_tests)) 25 | result.addTests(testscenarios.generate_scenarios(package_tests)) 26 | return result 27 | -------------------------------------------------------------------------------- /pbr/tests/base.py: -------------------------------------------------------------------------------- 1 | # Copyright 2010-2011 OpenStack Foundation 2 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | # Copyright (C) 2013 Association of Universities for Research in Astronomy 16 | # (AURA) 17 | # 18 | # Redistribution and use in source and binary forms, with or without 19 | # modification, are permitted provided that the following conditions are met: 20 | # 21 | # 1. Redistributions of source code must retain the above copyright 22 | # notice, this list of conditions and the following disclaimer. 23 | # 24 | # 2. Redistributions in binary form must reproduce the above 25 | # copyright notice, this list of conditions and the following 26 | # disclaimer in the documentation and/or other materials provided 27 | # with the distribution. 28 | # 29 | # 3. The name of AURA and its representatives may not be used to 30 | # endorse or promote products derived from this software without 31 | # specific prior written permission. 32 | # 33 | # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 34 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 35 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 36 | # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 37 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 38 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 39 | 40 | """Common utilities used in testing""" 41 | 42 | import os 43 | import shutil 44 | import subprocess 45 | import sys 46 | 47 | import fixtures 48 | import testresources 49 | import testtools 50 | from testtools import content 51 | 52 | from pbr import options 53 | 54 | 55 | class DiveDir(fixtures.Fixture): 56 | """Dive into given directory and return back on cleanup. 57 | 58 | :ivar path: The target directory. 59 | """ 60 | 61 | def __init__(self, path): 62 | self.path = path 63 | 64 | def setUp(self): 65 | super(DiveDir, self).setUp() 66 | self.addCleanup(os.chdir, os.getcwd()) 67 | os.chdir(self.path) 68 | 69 | 70 | class BaseTestCase(testtools.TestCase, testresources.ResourcedTestCase): 71 | 72 | def setUp(self): 73 | super(BaseTestCase, self).setUp() 74 | test_timeout = os.environ.get('OS_TEST_TIMEOUT', 30) 75 | try: 76 | test_timeout = int(test_timeout) 77 | except ValueError: 78 | # If timeout value is invalid, fail hard. 79 | print("OS_TEST_TIMEOUT set to invalid value" 80 | " defaulting to no timeout") 81 | test_timeout = 0 82 | if test_timeout > 0: 83 | self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) 84 | 85 | if os.environ.get('OS_STDOUT_CAPTURE') in options.TRUE_VALUES: 86 | stdout = self.useFixture(fixtures.StringStream('stdout')).stream 87 | self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout)) 88 | if os.environ.get('OS_STDERR_CAPTURE') in options.TRUE_VALUES: 89 | stderr = self.useFixture(fixtures.StringStream('stderr')).stream 90 | self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr)) 91 | self.log_fixture = self.useFixture( 92 | fixtures.FakeLogger('pbr')) 93 | 94 | # Older git does not have config --local, so create a temporary home 95 | # directory to permit using git config --global without stepping on 96 | # developer configuration. 97 | self.useFixture(fixtures.TempHomeDir()) 98 | self.useFixture(fixtures.NestedTempfile()) 99 | self.useFixture(fixtures.FakeLogger()) 100 | # TODO(lifeless) we should remove PBR_VERSION from the environment. 101 | # rather than setting it, because thats not representative - we need to 102 | # test non-preversioned codepaths too! 103 | self.useFixture(fixtures.EnvironmentVariable('PBR_VERSION', '0.0')) 104 | 105 | self.temp_dir = self.useFixture(fixtures.TempDir()).path 106 | self.package_dir = os.path.join(self.temp_dir, 'testpackage') 107 | shutil.copytree(os.path.join(os.path.dirname(__file__), 'testpackage'), 108 | self.package_dir) 109 | self.addCleanup(os.chdir, os.getcwd()) 110 | os.chdir(self.package_dir) 111 | self.addCleanup(self._discard_testpackage) 112 | # Tests can opt into non-PBR_VERSION by setting preversioned=False as 113 | # an attribute. 114 | if not getattr(self, 'preversioned', True): 115 | self.useFixture(fixtures.EnvironmentVariable('PBR_VERSION')) 116 | setup_cfg_path = os.path.join(self.package_dir, 'setup.cfg') 117 | with open(setup_cfg_path, 'rt') as cfg: 118 | content = cfg.read() 119 | content = content.replace(u'version = 0.1.dev', u'') 120 | with open(setup_cfg_path, 'wt') as cfg: 121 | cfg.write(content) 122 | 123 | def _discard_testpackage(self): 124 | # Remove pbr.testpackage from sys.modules so that it can be freshly 125 | # re-imported by the next test 126 | for k in list(sys.modules): 127 | if (k == 'pbr_testpackage' or 128 | k.startswith('pbr_testpackage.')): 129 | del sys.modules[k] 130 | 131 | def run_pbr(self, *args, **kwargs): 132 | return self._run_cmd('pbr', args, **kwargs) 133 | 134 | def run_setup(self, *args, **kwargs): 135 | return self._run_cmd(sys.executable, ('setup.py',) + args, **kwargs) 136 | 137 | def _run_cmd(self, cmd, args=[], allow_fail=True, cwd=None): 138 | """Run a command in the root of the test working copy. 139 | 140 | Runs a command, with the given argument list, in the root of the test 141 | working copy--returns the stdout and stderr streams and the exit code 142 | from the subprocess. 143 | 144 | :param cwd: If falsy run within the test package dir, otherwise run 145 | within the named path. 146 | """ 147 | cwd = cwd or self.package_dir 148 | result = _run_cmd([cmd] + list(args), cwd=cwd) 149 | if result[2] and not allow_fail: 150 | raise Exception("Command failed retcode=%s" % result[2]) 151 | return result 152 | 153 | 154 | class CapturedSubprocess(fixtures.Fixture): 155 | """Run a process and capture its output. 156 | 157 | :attr stdout: The output (a string). 158 | :attr stderr: The standard error (a string). 159 | :attr returncode: The return code of the process. 160 | 161 | Note that stdout and stderr are decoded from the bytestrings subprocess 162 | returns using error=replace. 163 | """ 164 | 165 | def __init__(self, label, *args, **kwargs): 166 | """Create a CapturedSubprocess. 167 | 168 | :param label: A label for the subprocess in the test log. E.g. 'foo'. 169 | :param *args: The *args to pass to Popen. 170 | :param **kwargs: The **kwargs to pass to Popen. 171 | """ 172 | super(CapturedSubprocess, self).__init__() 173 | self.label = label 174 | self.args = args 175 | self.kwargs = kwargs 176 | self.kwargs['stderr'] = subprocess.PIPE 177 | self.kwargs['stdin'] = subprocess.PIPE 178 | self.kwargs['stdout'] = subprocess.PIPE 179 | 180 | def setUp(self): 181 | super(CapturedSubprocess, self).setUp() 182 | proc = subprocess.Popen(*self.args, **self.kwargs) 183 | out, err = proc.communicate() 184 | self.out = out.decode('utf-8', 'replace') 185 | self.err = err.decode('utf-8', 'replace') 186 | self.addDetail(self.label + '-stdout', content.text_content(self.out)) 187 | self.addDetail(self.label + '-stderr', content.text_content(self.err)) 188 | self.returncode = proc.returncode 189 | if proc.returncode: 190 | raise AssertionError( 191 | 'Failed process args=%r, kwargs=%r, returncode=%s' % ( 192 | self.args, self.kwargs, proc.returncode)) 193 | self.addCleanup(delattr, self, 'out') 194 | self.addCleanup(delattr, self, 'err') 195 | self.addCleanup(delattr, self, 'returncode') 196 | 197 | 198 | def _run_cmd(args, cwd): 199 | """Run the command args in cwd. 200 | 201 | :param args: The command to run e.g. ['git', 'status'] 202 | :param cwd: The directory to run the comamnd in. 203 | :return: ((stdout, stderr), returncode) 204 | """ 205 | print('Running %s' % ' '.join(args)) 206 | p = subprocess.Popen( 207 | args, stdin=subprocess.PIPE, stdout=subprocess.PIPE, 208 | stderr=subprocess.PIPE, cwd=cwd) 209 | streams = tuple(s.decode('latin1').strip() for s in p.communicate()) 210 | print('STDOUT:') 211 | print(streams[0]) 212 | print('STDERR:') 213 | print(streams[1]) 214 | return (streams) + (p.returncode,) 215 | 216 | 217 | def _config_git(): 218 | _run_cmd( 219 | ['git', 'config', '--global', 'user.email', 'example@example.com'], 220 | None) 221 | _run_cmd( 222 | ['git', 'config', '--global', 'user.name', 'OpenStack Developer'], 223 | None) 224 | _run_cmd( 225 | ['git', 'config', '--global', 'user.signingkey', 226 | 'example@example.com'], None) 227 | -------------------------------------------------------------------------------- /pbr/tests/test_commands.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Copyright (C) 2013 Association of Universities for Research in Astronomy 17 | # (AURA) 18 | # 19 | # Redistribution and use in source and binary forms, with or without 20 | # modification, are permitted provided that the following conditions are met: 21 | # 22 | # 1. Redistributions of source code must retain the above copyright 23 | # notice, this list of conditions and the following disclaimer. 24 | # 25 | # 2. Redistributions in binary form must reproduce the above 26 | # copyright notice, this list of conditions and the following 27 | # disclaimer in the documentation and/or other materials provided 28 | # with the distribution. 29 | # 30 | # 3. The name of AURA and its representatives may not be used to 31 | # endorse or promote products derived from this software without 32 | # specific prior written permission. 33 | # 34 | # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 35 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 36 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37 | # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 38 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 39 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 40 | 41 | from testtools import content 42 | 43 | from pbr.tests import base 44 | 45 | 46 | class TestCommands(base.BaseTestCase): 47 | def test_custom_build_py_command(self): 48 | """Test custom build_py command. 49 | 50 | Test that a custom subclass of the build_py command runs when listed in 51 | the commands [global] option, rather than the normal build command. 52 | """ 53 | 54 | stdout, stderr, return_code = self.run_setup('build_py') 55 | self.addDetail('stdout', content.text_content(stdout)) 56 | self.addDetail('stderr', content.text_content(stderr)) 57 | self.assertIn('Running custom build_py command.', stdout) 58 | self.assertEqual(0, return_code) 59 | 60 | def test_custom_deb_version_py_command(self): 61 | """Test custom deb_version command.""" 62 | stdout, stderr, return_code = self.run_setup('deb_version') 63 | self.addDetail('stdout', content.text_content(stdout)) 64 | self.addDetail('stderr', content.text_content(stderr)) 65 | self.assertIn('Extracting deb version', stdout) 66 | self.assertEqual(0, return_code) 67 | 68 | def test_custom_rpm_version_py_command(self): 69 | """Test custom rpm_version command.""" 70 | stdout, stderr, return_code = self.run_setup('rpm_version') 71 | self.addDetail('stdout', content.text_content(stdout)) 72 | self.addDetail('stderr', content.text_content(stderr)) 73 | self.assertIn('Extracting rpm version', stdout) 74 | self.assertEqual(0, return_code) 75 | 76 | def test_freeze_command(self): 77 | """Test that freeze output is sorted in a case-insensitive manner.""" 78 | stdout, stderr, return_code = self.run_pbr('freeze') 79 | self.assertEqual(0, return_code) 80 | pkgs = [] 81 | for line in stdout.split('\n'): 82 | pkgs.append(line.split('==')[0].lower()) 83 | pkgs_sort = sorted(pkgs[:]) 84 | self.assertEqual(pkgs_sort, pkgs) 85 | -------------------------------------------------------------------------------- /pbr/tests/test_core.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Copyright (C) 2013 Association of Universities for Research in Astronomy 17 | # (AURA) 18 | # 19 | # Redistribution and use in source and binary forms, with or without 20 | # modification, are permitted provided that the following conditions are met: 21 | # 22 | # 1. Redistributions of source code must retain the above copyright 23 | # notice, this list of conditions and the following disclaimer. 24 | # 25 | # 2. Redistributions in binary form must reproduce the above 26 | # copyright notice, this list of conditions and the following 27 | # disclaimer in the documentation and/or other materials provided 28 | # with the distribution. 29 | # 30 | # 3. The name of AURA and its representatives may not be used to 31 | # endorse or promote products derived from this software without 32 | # specific prior written permission. 33 | # 34 | # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 35 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 36 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37 | # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 38 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 39 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 40 | 41 | import glob 42 | import os 43 | import sys 44 | import tarfile 45 | 46 | import fixtures 47 | 48 | from pbr.tests import base 49 | 50 | 51 | class TestCore(base.BaseTestCase): 52 | 53 | cmd_names = ('pbr_test_cmd', 'pbr_test_cmd_with_class') 54 | 55 | def check_script_install(self, install_stdout): 56 | for cmd_name in self.cmd_names: 57 | install_txt = 'Installing %s script to %s' % (cmd_name, 58 | self.temp_dir) 59 | self.assertIn(install_txt, install_stdout) 60 | 61 | cmd_filename = os.path.join(self.temp_dir, cmd_name) 62 | 63 | script_txt = open(cmd_filename, 'r').read() 64 | self.assertNotIn('pkg_resources', script_txt) 65 | 66 | stdout, _, return_code = self._run_cmd(cmd_filename) 67 | self.assertIn("PBR", stdout) 68 | 69 | def test_setup_py_keywords(self): 70 | """setup.py --keywords. 71 | 72 | Test that the `./setup.py --keywords` command returns the correct 73 | value without balking. 74 | """ 75 | 76 | self.run_setup('egg_info') 77 | stdout, _, _ = self.run_setup('--keywords') 78 | assert stdout == 'packaging, distutils, setuptools' 79 | 80 | def test_sdist_extra_files(self): 81 | """Test that the extra files are correctly added.""" 82 | 83 | stdout, _, return_code = self.run_setup('sdist', '--formats=gztar') 84 | 85 | # There can be only one 86 | try: 87 | tf_path = glob.glob(os.path.join('dist', '*.tar.gz'))[0] 88 | except IndexError: 89 | assert False, 'source dist not found' 90 | 91 | tf = tarfile.open(tf_path) 92 | names = ['/'.join(p.split('/')[1:]) for p in tf.getnames()] 93 | 94 | self.assertIn('extra-file.txt', names) 95 | 96 | def test_console_script_install(self): 97 | """Test that we install a non-pkg-resources console script.""" 98 | 99 | if os.name == 'nt': 100 | self.skipTest('Windows support is passthrough') 101 | 102 | stdout, _, return_code = self.run_setup( 103 | 'install_scripts', '--install-dir=%s' % self.temp_dir) 104 | 105 | self.useFixture( 106 | fixtures.EnvironmentVariable('PYTHONPATH', '.')) 107 | 108 | self.check_script_install(stdout) 109 | 110 | def test_console_script_develop(self): 111 | """Test that we develop a non-pkg-resources console script.""" 112 | 113 | if sys.version_info < (3, 0): 114 | self.skipTest( 115 | 'Fails with recent virtualenv due to ' 116 | 'https://github.com/pypa/virtualenv/issues/1638' 117 | ) 118 | 119 | if os.name == 'nt': 120 | self.skipTest('Windows support is passthrough') 121 | 122 | self.useFixture( 123 | fixtures.EnvironmentVariable( 124 | 'PYTHONPATH', ".:%s" % self.temp_dir)) 125 | 126 | stdout, _, return_code = self.run_setup( 127 | 'develop', '--install-dir=%s' % self.temp_dir) 128 | 129 | self.check_script_install(stdout) 130 | 131 | 132 | class TestGitSDist(base.BaseTestCase): 133 | 134 | def setUp(self): 135 | super(TestGitSDist, self).setUp() 136 | 137 | stdout, _, return_code = self._run_cmd('git', ('init',)) 138 | if return_code: 139 | self.skipTest("git not installed") 140 | 141 | stdout, _, return_code = self._run_cmd('git', ('add', '.')) 142 | stdout, _, return_code = self._run_cmd( 143 | 'git', ('commit', '-m', 'Turn this into a git repo')) 144 | 145 | stdout, _, return_code = self.run_setup('sdist', '--formats=gztar') 146 | 147 | def test_sdist_git_extra_files(self): 148 | """Test that extra files found in git are correctly added.""" 149 | # There can be only one 150 | tf_path = glob.glob(os.path.join('dist', '*.tar.gz'))[0] 151 | tf = tarfile.open(tf_path) 152 | names = ['/'.join(p.split('/')[1:]) for p in tf.getnames()] 153 | 154 | self.assertIn('git-extra-file.txt', names) 155 | -------------------------------------------------------------------------------- /pbr/tests/test_files.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # All Rights Reserved. 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | from __future__ import print_function 17 | 18 | import os 19 | 20 | import fixtures 21 | 22 | from pbr.hooks import files 23 | from pbr.tests import base 24 | 25 | 26 | class FilesConfigTest(base.BaseTestCase): 27 | 28 | def setUp(self): 29 | super(FilesConfigTest, self).setUp() 30 | 31 | pkg_fixture = fixtures.PythonPackage( 32 | "fake_package", [ 33 | ("fake_module.py", b""), 34 | ("other_fake_module.py", b""), 35 | ]) 36 | self.useFixture(pkg_fixture) 37 | pkg_etc = os.path.join(pkg_fixture.base, 'etc') 38 | pkg_ansible = os.path.join(pkg_fixture.base, 'ansible', 39 | 'kolla-ansible', 'test') 40 | dir_spcs = os.path.join(pkg_fixture.base, 'dir with space') 41 | dir_subdir_spc = os.path.join(pkg_fixture.base, 'multi space', 42 | 'more spaces') 43 | pkg_sub = os.path.join(pkg_etc, 'sub') 44 | subpackage = os.path.join( 45 | pkg_fixture.base, 'fake_package', 'subpackage') 46 | os.makedirs(pkg_sub) 47 | os.makedirs(subpackage) 48 | os.makedirs(pkg_ansible) 49 | os.makedirs(dir_spcs) 50 | os.makedirs(dir_subdir_spc) 51 | with open(os.path.join(pkg_etc, "foo"), 'w') as foo_file: 52 | foo_file.write("Foo Data") 53 | with open(os.path.join(pkg_sub, "bar"), 'w') as foo_file: 54 | foo_file.write("Bar Data") 55 | with open(os.path.join(pkg_ansible, "baz"), 'w') as baz_file: 56 | baz_file.write("Baz Data") 57 | with open(os.path.join(subpackage, "__init__.py"), 'w') as foo_file: 58 | foo_file.write("# empty") 59 | with open(os.path.join(dir_spcs, "file with spc"), 'w') as spc_file: 60 | spc_file.write("# empty") 61 | with open(os.path.join(dir_subdir_spc, "file with spc"), 'w') as file_: 62 | file_.write("# empty") 63 | 64 | self.useFixture(base.DiveDir(pkg_fixture.base)) 65 | 66 | def test_implicit_auto_package(self): 67 | config = dict( 68 | files=dict( 69 | ) 70 | ) 71 | files.FilesConfig(config, 'fake_package').run() 72 | self.assertIn('subpackage', config['files']['packages']) 73 | 74 | def test_auto_package(self): 75 | config = dict( 76 | files=dict( 77 | packages='fake_package', 78 | ) 79 | ) 80 | files.FilesConfig(config, 'fake_package').run() 81 | self.assertIn('subpackage', config['files']['packages']) 82 | 83 | def test_data_files_globbing(self): 84 | config = dict( 85 | files=dict( 86 | data_files="\n etc/pbr = etc/*" 87 | ) 88 | ) 89 | files.FilesConfig(config, 'fake_package').run() 90 | self.assertIn( 91 | "\n'etc/pbr/' = \n 'etc/foo'\n'etc/pbr/sub' = \n 'etc/sub/bar'", 92 | config['files']['data_files']) 93 | 94 | def test_data_files_with_spaces(self): 95 | config = dict( 96 | files=dict( 97 | data_files="\n 'i like spaces' = 'dir with space'/*" 98 | ) 99 | ) 100 | files.FilesConfig(config, 'fake_package').run() 101 | self.assertIn( 102 | "\n'i like spaces/' = \n 'dir with space/file with spc'", 103 | config['files']['data_files']) 104 | 105 | def test_data_files_with_spaces_subdirectories(self): 106 | # test that we can handle whitespace in subdirectories 107 | data_files = "\n 'one space/two space' = 'multi space/more spaces'/*" 108 | expected = ( 109 | "\n'one space/two space/' = " 110 | "\n 'multi space/more spaces/file with spc'") 111 | config = dict( 112 | files=dict( 113 | data_files=data_files 114 | ) 115 | ) 116 | files.FilesConfig(config, 'fake_package').run() 117 | self.assertIn(expected, config['files']['data_files']) 118 | 119 | def test_data_files_with_spaces_quoted_components(self): 120 | # test that we can quote individual path components 121 | data_files = ( 122 | "\n'one space'/'two space' = 'multi space'/'more spaces'/*" 123 | ) 124 | expected = ("\n'one space/two space/' = " 125 | "\n 'multi space/more spaces/file with spc'") 126 | config = dict( 127 | files=dict( 128 | data_files=data_files 129 | ) 130 | ) 131 | files.FilesConfig(config, 'fake_package').run() 132 | self.assertIn(expected, config['files']['data_files']) 133 | 134 | def test_data_files_globbing_source_prefix_in_directory_name(self): 135 | # We want to test that the string, "docs", is not replaced in a 136 | # subdirectory name, "sub-docs" 137 | config = dict( 138 | files=dict( 139 | data_files="\n share/ansible = ansible/*" 140 | ) 141 | ) 142 | files.FilesConfig(config, 'fake_package').run() 143 | self.assertIn( 144 | "\n'share/ansible/' = " 145 | "\n'share/ansible/kolla-ansible' = " 146 | "\n'share/ansible/kolla-ansible/test' = " 147 | "\n 'ansible/kolla-ansible/test/baz'", 148 | config['files']['data_files']) 149 | -------------------------------------------------------------------------------- /pbr/tests/test_hooks.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Copyright (C) 2013 Association of Universities for Research in Astronomy 17 | # (AURA) 18 | # 19 | # Redistribution and use in source and binary forms, with or without 20 | # modification, are permitted provided that the following conditions are met: 21 | # 22 | # 1. Redistributions of source code must retain the above copyright 23 | # notice, this list of conditions and the following disclaimer. 24 | # 25 | # 2. Redistributions in binary form must reproduce the above 26 | # copyright notice, this list of conditions and the following 27 | # disclaimer in the documentation and/or other materials provided 28 | # with the distribution. 29 | # 30 | # 3. The name of AURA and its representatives may not be used to 31 | # endorse or promote products derived from this software without 32 | # specific prior written permission. 33 | # 34 | # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 35 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 36 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37 | # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 38 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 39 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 40 | 41 | import os 42 | 43 | from testtools import matchers 44 | from testtools import skipUnless 45 | 46 | from pbr import testr_command 47 | from pbr.tests import base 48 | from pbr.tests import util 49 | 50 | 51 | class TestHooks(base.BaseTestCase): 52 | def setUp(self): 53 | super(TestHooks, self).setUp() 54 | with util.open_config( 55 | os.path.join(self.package_dir, 'setup.cfg')) as cfg: 56 | cfg.set('global', 'setup-hooks', 57 | 'pbr_testpackage._setup_hooks.test_hook_1\n' 58 | 'pbr_testpackage._setup_hooks.test_hook_2') 59 | 60 | def test_global_setup_hooks(self): 61 | """Test setup_hooks. 62 | 63 | Test that setup_hooks listed in the [global] section of setup.cfg are 64 | executed in order. 65 | """ 66 | 67 | stdout, _, return_code = self.run_setup('egg_info') 68 | assert 'test_hook_1\ntest_hook_2' in stdout 69 | assert return_code == 0 70 | 71 | @skipUnless(testr_command.have_testr, "testrepository not available") 72 | def test_custom_commands_known(self): 73 | stdout, _, return_code = self.run_setup('--help-commands') 74 | self.assertFalse(return_code) 75 | self.assertThat(stdout, matchers.Contains(" testr ")) 76 | -------------------------------------------------------------------------------- /pbr/tests/test_pbr_json.py: -------------------------------------------------------------------------------- 1 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 2 | # not use this file except in compliance with the License. You may obtain 3 | # a copy of the License at 4 | # 5 | # http://www.apache.org/licenses/LICENSE-2.0 6 | # 7 | # Unless required by applicable law or agreed to in writing, software 8 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 9 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 10 | # License for the specific language governing permissions and limitations 11 | # under the License. 12 | 13 | try: 14 | from unittest import mock 15 | except ImportError: 16 | import mock 17 | 18 | from pbr import pbr_json 19 | from pbr.tests import base 20 | 21 | 22 | class TestJsonContent(base.BaseTestCase): 23 | @mock.patch('pbr.git._run_git_functions', return_value=True) 24 | @mock.patch('pbr.git.get_git_short_sha', return_value="123456") 25 | @mock.patch('pbr.git.get_is_release', return_value=True) 26 | def test_content(self, mock_get_is, mock_get_git, mock_run): 27 | cmd = mock.Mock() 28 | pbr_json.write_pbr_json(cmd, "basename", "pbr.json") 29 | cmd.write_file.assert_called_once_with( 30 | 'pbr', 31 | 'pbr.json', 32 | '{"git_version": "123456", "is_release": true}' 33 | ) 34 | -------------------------------------------------------------------------------- /pbr/tests/test_setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2011 OpenStack Foundation 2 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 3 | # All Rights Reserved. 4 | # 5 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 6 | # not use this file except in compliance with the License. You may obtain 7 | # a copy of the License at 8 | # 9 | # http://www.apache.org/licenses/LICENSE-2.0 10 | # 11 | # Unless required by applicable law or agreed to in writing, software 12 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 13 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 14 | # License for the specific language governing permissions and limitations 15 | # under the License. 16 | 17 | from __future__ import print_function 18 | 19 | import os 20 | 21 | try: 22 | import cStringIO as io 23 | BytesIO = io.StringIO 24 | except ImportError: 25 | import io 26 | BytesIO = io.BytesIO 27 | 28 | import fixtures 29 | 30 | from pbr import git 31 | from pbr import options 32 | from pbr.tests import base 33 | 34 | 35 | class SkipFileWrites(base.BaseTestCase): 36 | 37 | scenarios = [ 38 | ('changelog_option_true', 39 | dict(option_key='skip_changelog', option_value='True', 40 | env_key='SKIP_WRITE_GIT_CHANGELOG', env_value=None, 41 | pkg_func=git.write_git_changelog, filename='ChangeLog')), 42 | ('changelog_option_false', 43 | dict(option_key='skip_changelog', option_value='False', 44 | env_key='SKIP_WRITE_GIT_CHANGELOG', env_value=None, 45 | pkg_func=git.write_git_changelog, filename='ChangeLog')), 46 | ('changelog_env_true', 47 | dict(option_key='skip_changelog', option_value='False', 48 | env_key='SKIP_WRITE_GIT_CHANGELOG', env_value='True', 49 | pkg_func=git.write_git_changelog, filename='ChangeLog')), 50 | ('changelog_both_true', 51 | dict(option_key='skip_changelog', option_value='True', 52 | env_key='SKIP_WRITE_GIT_CHANGELOG', env_value='True', 53 | pkg_func=git.write_git_changelog, filename='ChangeLog')), 54 | ('authors_option_true', 55 | dict(option_key='skip_authors', option_value='True', 56 | env_key='SKIP_GENERATE_AUTHORS', env_value=None, 57 | pkg_func=git.generate_authors, filename='AUTHORS')), 58 | ('authors_option_false', 59 | dict(option_key='skip_authors', option_value='False', 60 | env_key='SKIP_GENERATE_AUTHORS', env_value=None, 61 | pkg_func=git.generate_authors, filename='AUTHORS')), 62 | ('authors_env_true', 63 | dict(option_key='skip_authors', option_value='False', 64 | env_key='SKIP_GENERATE_AUTHORS', env_value='True', 65 | pkg_func=git.generate_authors, filename='AUTHORS')), 66 | ('authors_both_true', 67 | dict(option_key='skip_authors', option_value='True', 68 | env_key='SKIP_GENERATE_AUTHORS', env_value='True', 69 | pkg_func=git.generate_authors, filename='AUTHORS')), 70 | ] 71 | 72 | def setUp(self): 73 | super(SkipFileWrites, self).setUp() 74 | self.temp_path = self.useFixture(fixtures.TempDir()).path 75 | self.root_dir = os.path.abspath(os.path.curdir) 76 | self.git_dir = os.path.join(self.root_dir, ".git") 77 | if not os.path.exists(self.git_dir): 78 | self.skipTest("%s is missing; skipping git-related checks" 79 | % self.git_dir) 80 | return 81 | self.filename = os.path.join(self.temp_path, self.filename) 82 | self.option_dict = dict() 83 | if self.option_key is not None: 84 | self.option_dict[self.option_key] = ('setup.cfg', 85 | self.option_value) 86 | self.useFixture( 87 | fixtures.EnvironmentVariable(self.env_key, self.env_value)) 88 | 89 | def test_skip(self): 90 | self.pkg_func(git_dir=self.git_dir, 91 | dest_dir=self.temp_path, 92 | option_dict=self.option_dict) 93 | self.assertEqual( 94 | not os.path.exists(self.filename), 95 | (self.option_value.lower() in options.TRUE_VALUES or 96 | self.env_value is not None)) 97 | 98 | 99 | _changelog_content = """7780758\x00Break parser\x00 (tag: refs/tags/1_foo.1) 100 | 04316fe\x00Make python\x00 (refs/heads/review/monty_taylor/27519) 101 | 378261a\x00Add an integration test script.\x00 102 | 3c373ac\x00Merge "Lib\x00 (HEAD, tag: refs/tags/2013.2.rc2, tag: refs/tags/2013.2, refs/heads/mile-proposed) 103 | 182feb3\x00Fix pip invocation for old versions of pip.\x00 (tag: refs/tags/0.5.17) 104 | fa4f46e\x00Remove explicit depend on distribute.\x00 (tag: refs/tags/0.5.16) 105 | d1c53dd\x00Use pip instead of easy_install for installation.\x00 106 | a793ea1\x00Merge "Skip git-checkout related tests when .git is missing"\x00 107 | 6c27ce7\x00Skip git-checkout related tests when .git is missing\x00 108 | 451e513\x00Bug fix: create_stack() fails when waiting\x00 109 | 4c8cfe4\x00Improve test coverage: network delete API\x00 (tag: refs/tags/(evil)) 110 | d7e6167\x00Bug fix: Fix pass thru filtering in list_networks\x00 (tag: refs/tags/ev()il) 111 | c47ec15\x00Consider 'in-use' a non-pending volume for caching\x00 (tag: refs/tags/ev)il) 112 | 8696fbd\x00Improve test coverage: private extension API\x00 (tag: refs/tags/ev(il) 113 | f0440f8\x00Improve test coverage: hypervisor list\x00 (tag: refs/tags/e(vi)l) 114 | 04984a5\x00Refactor hooks file.\x00 (HEAD, tag: 0.6.7,b, tag: refs/tags/(12), refs/heads/master) 115 | a65e8ee\x00Remove jinja pin.\x00 (tag: refs/tags/0.5.14, tag: refs/tags/0.5.13) 116 | """ # noqa 117 | 118 | 119 | def _make_old_git_changelog_format(line): 120 | """Convert post-1.8.1 git log format to pre-1.8.1 git log format""" 121 | 122 | if not line.strip(): 123 | return line 124 | sha, msg, refname = line.split('\x00') 125 | refname = refname.replace('tag: ', '') 126 | return '\x00'.join((sha, msg, refname)) 127 | 128 | 129 | _old_git_changelog_content = '\n'.join( 130 | _make_old_git_changelog_format(line) 131 | for line in _changelog_content.split('\n')) 132 | 133 | 134 | class GitLogsTest(base.BaseTestCase): 135 | 136 | scenarios = [ 137 | ('pre1.8.3', {'changelog': _old_git_changelog_content}), 138 | ('post1.8.3', {'changelog': _changelog_content}), 139 | ] 140 | 141 | def setUp(self): 142 | super(GitLogsTest, self).setUp() 143 | self.temp_path = self.useFixture(fixtures.TempDir()).path 144 | self.root_dir = os.path.abspath(os.path.curdir) 145 | self.git_dir = os.path.join(self.root_dir, ".git") 146 | self.useFixture( 147 | fixtures.EnvironmentVariable('SKIP_GENERATE_AUTHORS')) 148 | self.useFixture( 149 | fixtures.EnvironmentVariable('SKIP_WRITE_GIT_CHANGELOG')) 150 | 151 | def test_write_git_changelog(self): 152 | self.useFixture(fixtures.FakePopen(lambda _: { 153 | "stdout": BytesIO(self.changelog.encode('utf-8')) 154 | })) 155 | 156 | git.write_git_changelog(git_dir=self.git_dir, 157 | dest_dir=self.temp_path) 158 | 159 | with open(os.path.join(self.temp_path, "ChangeLog"), "r") as ch_fh: 160 | changelog_contents = ch_fh.read() 161 | self.assertIn("2013.2", changelog_contents) 162 | self.assertIn("0.5.17", changelog_contents) 163 | self.assertIn("------", changelog_contents) 164 | self.assertIn("Refactor hooks file", changelog_contents) 165 | self.assertIn( 166 | r"Bug fix: create\_stack() fails when waiting", 167 | changelog_contents) 168 | self.assertNotIn("Refactor hooks file.", changelog_contents) 169 | self.assertNotIn("182feb3", changelog_contents) 170 | self.assertNotIn("review/monty_taylor/27519", changelog_contents) 171 | self.assertNotIn("0.5.13", changelog_contents) 172 | self.assertNotIn("0.6.7", changelog_contents) 173 | self.assertNotIn("12", changelog_contents) 174 | self.assertNotIn("(evil)", changelog_contents) 175 | self.assertNotIn("ev()il", changelog_contents) 176 | self.assertNotIn("ev(il", changelog_contents) 177 | self.assertNotIn("ev)il", changelog_contents) 178 | self.assertNotIn("e(vi)l", changelog_contents) 179 | self.assertNotIn('Merge "', changelog_contents) 180 | self.assertNotIn(r'1\_foo.1', changelog_contents) 181 | 182 | def test_generate_authors(self): 183 | author_old = u"Foo Foo " 184 | author_new = u"Bar Bar " 185 | co_author = u"Foo Bar " 186 | co_author_by = u"Co-authored-by: " + co_author 187 | 188 | git_log_cmd = ( 189 | "git --git-dir=%s log --format=%%aN <%%aE>" 190 | % self.git_dir) 191 | git_co_log_cmd = ("git --git-dir=%s log" % self.git_dir) 192 | git_top_level = "git rev-parse --show-toplevel" 193 | cmd_map = { 194 | git_log_cmd: author_new, 195 | git_co_log_cmd: co_author_by, 196 | git_top_level: self.root_dir, 197 | } 198 | 199 | exist_files = [self.git_dir, 200 | os.path.join(self.temp_path, "AUTHORS.in")] 201 | self.useFixture(fixtures.MonkeyPatch( 202 | "os.path.exists", 203 | lambda path: os.path.abspath(path) in exist_files)) 204 | 205 | def _fake_run_shell_command(cmd, **kwargs): 206 | return cmd_map[" ".join(cmd)] 207 | 208 | self.useFixture(fixtures.MonkeyPatch( 209 | "pbr.git._run_shell_command", 210 | _fake_run_shell_command)) 211 | 212 | with open(os.path.join(self.temp_path, "AUTHORS.in"), "w") as auth_fh: 213 | auth_fh.write("%s\n" % author_old) 214 | 215 | git.generate_authors(git_dir=self.git_dir, 216 | dest_dir=self.temp_path) 217 | 218 | with open(os.path.join(self.temp_path, "AUTHORS"), "r") as auth_fh: 219 | authors = auth_fh.read() 220 | self.assertIn(author_old, authors) 221 | self.assertIn(author_new, authors) 222 | self.assertIn(co_author, authors) 223 | -------------------------------------------------------------------------------- /pbr/tests/test_util.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Copyright (c) 2015 Hewlett-Packard Development Company, L.P. (HP) 3 | # 4 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 5 | # not use this file except in compliance with the License. You may obtain 6 | # a copy of the License at 7 | # 8 | # http://www.apache.org/licenses/LICENSE-2.0 9 | # 10 | # Unless required by applicable law or agreed to in writing, software 11 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 12 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 13 | # License for the specific language governing permissions and limitations 14 | # under the License. 15 | 16 | try: 17 | import configparser 18 | except ImportError: 19 | import ConfigParser as configparser 20 | import io 21 | import tempfile 22 | import textwrap 23 | 24 | import sys 25 | 26 | from pbr.tests import base 27 | from pbr import util 28 | 29 | 30 | def config_from_ini(ini): 31 | config = {} 32 | ini = textwrap.dedent(ini) 33 | if sys.version_info >= (3, 2): 34 | parser = configparser.ConfigParser() 35 | parser.read_file(io.StringIO(ini)) 36 | else: 37 | parser = configparser.SafeConfigParser() 38 | parser.readfp(io.StringIO(ini)) 39 | for section in parser.sections(): 40 | config[section] = dict(parser.items(section)) 41 | return config 42 | 43 | 44 | class TestBasics(base.BaseTestCase): 45 | 46 | def test_basics(self): 47 | self.maxDiff = None 48 | config_text = u""" 49 | [metadata] 50 | name = foo 51 | version = 1.0 52 | author = John Doe 53 | author_email = jd@example.com 54 | maintainer = Jim Burke 55 | maintainer_email = jb@example.com 56 | home_page = http://example.com 57 | summary = A foobar project. 58 | description = Hello, world. This is a long description. 59 | download_url = http://opendev.org/x/pbr 60 | classifier = 61 | Development Status :: 5 - Production/Stable 62 | Programming Language :: Python 63 | platform = 64 | any 65 | license = Apache 2.0 66 | requires_dist = 67 | Sphinx 68 | requests 69 | setup_requires_dist = 70 | docutils 71 | python_requires = >=3.6 72 | provides_dist = 73 | bax 74 | provides_extras = 75 | bar 76 | obsoletes_dist = 77 | baz 78 | 79 | [files] 80 | packages_root = src 81 | packages = 82 | foo 83 | package_data = 84 | "" = *.txt, *.rst 85 | foo = *.msg 86 | namespace_packages = 87 | hello 88 | data_files = 89 | bitmaps = 90 | bm/b1.gif 91 | bm/b2.gif 92 | config = 93 | cfg/data.cfg 94 | scripts = 95 | scripts/hello-world.py 96 | modules = 97 | mod1 98 | """ 99 | expected = { 100 | 'name': u'foo', 101 | 'version': u'1.0', 102 | 'author': u'John Doe', 103 | 'author_email': u'jd@example.com', 104 | 'maintainer': u'Jim Burke', 105 | 'maintainer_email': u'jb@example.com', 106 | 'url': u'http://example.com', 107 | 'description': u'A foobar project.', 108 | 'long_description': u'Hello, world. This is a long description.', 109 | 'download_url': u'http://opendev.org/x/pbr', 110 | 'classifiers': [ 111 | u'Development Status :: 5 - Production/Stable', 112 | u'Programming Language :: Python', 113 | ], 114 | 'platforms': [u'any'], 115 | 'license': u'Apache 2.0', 116 | 'install_requires': [ 117 | u'Sphinx', 118 | u'requests', 119 | ], 120 | 'setup_requires': [u'docutils'], 121 | 'python_requires': u'>=3.6', 122 | 'provides': [u'bax'], 123 | 'provides_extras': [u'bar'], 124 | 'obsoletes': [u'baz'], 125 | 'extras_require': {}, 126 | 127 | 'package_dir': {'': u'src'}, 128 | 'packages': [u'foo'], 129 | 'package_data': { 130 | '': ['*.txt,', '*.rst'], 131 | 'foo': ['*.msg'], 132 | }, 133 | 'namespace_packages': [u'hello'], 134 | 'data_files': [ 135 | ('bitmaps', ['bm/b1.gif', 'bm/b2.gif']), 136 | ('config', ['cfg/data.cfg']), 137 | ], 138 | 'scripts': [u'scripts/hello-world.py'], 139 | 'py_modules': [u'mod1'], 140 | } 141 | config = config_from_ini(config_text) 142 | actual = util.setup_cfg_to_setup_kwargs(config) 143 | self.assertDictEqual(expected, actual) 144 | 145 | 146 | class TestExtrasRequireParsingScenarios(base.BaseTestCase): 147 | 148 | scenarios = [ 149 | ('simple_extras', { 150 | 'config_text': u""" 151 | [extras] 152 | first = 153 | foo 154 | bar==1.0 155 | second = 156 | baz>=3.2 157 | foo 158 | """, 159 | 'expected_extra_requires': { 160 | 'first': ['foo', 'bar==1.0'], 161 | 'second': ['baz>=3.2', 'foo'], 162 | 'test': ['requests-mock'], 163 | "test:(python_version=='2.6')": ['ordereddict'], 164 | } 165 | }), 166 | ('with_markers', { 167 | 'config_text': u""" 168 | [extras] 169 | test = 170 | foo:python_version=='2.6' 171 | bar 172 | baz<1.6 :python_version=='2.6' 173 | zaz :python_version>'1.0' 174 | """, 175 | 'expected_extra_requires': { 176 | "test:(python_version=='2.6')": ['foo', 'baz<1.6'], 177 | "test": ['bar', 'zaz']}}), 178 | ('no_extras', { 179 | 'config_text': u""" 180 | [metadata] 181 | long_description = foo 182 | """, 183 | 'expected_extra_requires': 184 | {} 185 | })] 186 | 187 | def test_extras_parsing(self): 188 | config = config_from_ini(self.config_text) 189 | kwargs = util.setup_cfg_to_setup_kwargs(config) 190 | 191 | self.assertEqual(self.expected_extra_requires, 192 | kwargs['extras_require']) 193 | 194 | 195 | class TestInvalidMarkers(base.BaseTestCase): 196 | 197 | def test_invalid_marker_raises_error(self): 198 | config = {'extras': {'test': "foo :bad_marker>'1.0'"}} 199 | self.assertRaises(SyntaxError, util.setup_cfg_to_setup_kwargs, config) 200 | 201 | 202 | class TestMapFieldsParsingScenarios(base.BaseTestCase): 203 | 204 | scenarios = [ 205 | ('simple_project_urls', { 206 | 'config_text': u""" 207 | [metadata] 208 | project_urls = 209 | Bug Tracker = https://bugs.launchpad.net/pbr/ 210 | Documentation = https://docs.openstack.org/pbr/ 211 | Source Code = https://opendev.org/openstack/pbr 212 | """, # noqa: E501 213 | 'expected_project_urls': { 214 | 'Bug Tracker': 'https://bugs.launchpad.net/pbr/', 215 | 'Documentation': 'https://docs.openstack.org/pbr/', 216 | 'Source Code': 'https://opendev.org/openstack/pbr', 217 | }, 218 | }), 219 | ('query_parameters', { 220 | 'config_text': u""" 221 | [metadata] 222 | project_urls = 223 | Bug Tracker = https://bugs.launchpad.net/pbr/?query=true 224 | Documentation = https://docs.openstack.org/pbr/?foo=bar 225 | Source Code = https://git.openstack.org/cgit/openstack-dev/pbr/commit/?id=hash 226 | """, # noqa: E501 227 | 'expected_project_urls': { 228 | 'Bug Tracker': 'https://bugs.launchpad.net/pbr/?query=true', 229 | 'Documentation': 'https://docs.openstack.org/pbr/?foo=bar', 230 | 'Source Code': 'https://git.openstack.org/cgit/openstack-dev/pbr/commit/?id=hash', # noqa: E501 231 | }, 232 | }), 233 | ] 234 | 235 | def test_project_url_parsing(self): 236 | config = config_from_ini(self.config_text) 237 | kwargs = util.setup_cfg_to_setup_kwargs(config) 238 | 239 | self.assertEqual(self.expected_project_urls, kwargs['project_urls']) 240 | 241 | 242 | class TestKeywordsParsingScenarios(base.BaseTestCase): 243 | 244 | scenarios = [ 245 | ('keywords_list', { 246 | 'config_text': u""" 247 | [metadata] 248 | keywords = 249 | one 250 | two 251 | three 252 | """, # noqa: E501 253 | 'expected_keywords': ['one', 'two', 'three'], 254 | }, 255 | ), 256 | ('inline_keywords', { 257 | 'config_text': u""" 258 | [metadata] 259 | keywords = one, two, three 260 | """, # noqa: E501 261 | 'expected_keywords': ['one, two, three'], 262 | }), 263 | ] 264 | 265 | def test_keywords_parsing(self): 266 | config = config_from_ini(self.config_text) 267 | kwargs = util.setup_cfg_to_setup_kwargs(config) 268 | 269 | self.assertEqual(self.expected_keywords, kwargs['keywords']) 270 | 271 | 272 | class TestProvidesExtras(base.BaseTestCase): 273 | def test_provides_extras(self): 274 | ini = u""" 275 | [metadata] 276 | provides_extras = foo 277 | bar 278 | """ 279 | config = config_from_ini(ini) 280 | kwargs = util.setup_cfg_to_setup_kwargs(config) 281 | self.assertEqual(['foo', 'bar'], kwargs['provides_extras']) 282 | 283 | 284 | class TestDataFilesParsing(base.BaseTestCase): 285 | 286 | scenarios = [ 287 | ('data_files', { 288 | 'config_text': u""" 289 | [files] 290 | data_files = 291 | 'i like spaces/' = 292 | 'dir with space/file with spc 2' 293 | 'dir with space/file with spc 1' 294 | """, 295 | 'data_files': [ 296 | ('i like spaces/', ['dir with space/file with spc 2', 297 | 'dir with space/file with spc 1']) 298 | ] 299 | })] 300 | 301 | def test_handling_of_whitespace_in_data_files(self): 302 | config = config_from_ini(self.config_text) 303 | kwargs = util.setup_cfg_to_setup_kwargs(config) 304 | 305 | self.assertEqual(self.data_files, kwargs['data_files']) 306 | 307 | 308 | class TestUTF8DescriptionFile(base.BaseTestCase): 309 | def test_utf8_description_file(self): 310 | _, path = tempfile.mkstemp() 311 | ini_template = u""" 312 | [metadata] 313 | description_file = %s 314 | """ 315 | # Two \n's because pbr strips the file content and adds \n\n 316 | # This way we can use it directly as the assert comparison 317 | unicode_description = u'UTF8 description: é"…-ʃŋ\'\n\n' 318 | ini = ini_template % path 319 | with io.open(path, 'w', encoding='utf8') as f: 320 | f.write(unicode_description) 321 | config = config_from_ini(ini) 322 | kwargs = util.setup_cfg_to_setup_kwargs(config) 323 | self.assertEqual(unicode_description, kwargs['long_description']) 324 | -------------------------------------------------------------------------------- /pbr/tests/test_wsgi.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2015 Hewlett-Packard Development Company, L.P. (HP) 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); you may 4 | # not use this file except in compliance with the License. You may obtain 5 | # a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT 11 | # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the 12 | # License for the specific language governing permissions and limitations 13 | # under the License. 14 | 15 | import os 16 | import re 17 | import subprocess 18 | import sys 19 | try: 20 | # python 2 21 | from urllib2 import urlopen 22 | except ImportError: 23 | # python 3 24 | from urllib.request import urlopen 25 | 26 | from pbr.tests import base 27 | 28 | 29 | class TestWsgiScripts(base.BaseTestCase): 30 | 31 | cmd_names = ('pbr_test_wsgi', 'pbr_test_wsgi_with_class') 32 | 33 | def _get_path(self): 34 | if os.path.isdir("%s/lib64" % self.temp_dir): 35 | path = "%s/lib64" % self.temp_dir 36 | elif os.path.isdir("%s/lib" % self.temp_dir): 37 | path = "%s/lib" % self.temp_dir 38 | elif os.path.isdir("%s/site-packages" % self.temp_dir): 39 | return ".:%s/site-packages" % self.temp_dir 40 | else: 41 | raise Exception("Could not determine path for test") 42 | return ".:%s/python%s.%s/site-packages" % ( 43 | path, 44 | sys.version_info[0], 45 | sys.version_info[1]) 46 | 47 | def test_wsgi_script_install(self): 48 | """Test that we install a non-pkg-resources wsgi script.""" 49 | if os.name == 'nt': 50 | self.skipTest('Windows support is passthrough') 51 | 52 | stdout, _, return_code = self.run_setup( 53 | 'install', '--prefix=%s' % self.temp_dir) 54 | 55 | self._check_wsgi_install_content(stdout) 56 | 57 | def test_wsgi_script_run(self): 58 | """Test that we install a runnable wsgi script. 59 | 60 | This test actually attempts to start and interact with the 61 | wsgi script in question to demonstrate that it's a working 62 | wsgi script using simple server. 63 | 64 | """ 65 | if os.name == 'nt': 66 | self.skipTest('Windows support is passthrough') 67 | 68 | stdout, _, return_code = self.run_setup( 69 | 'install', '--prefix=%s' % self.temp_dir) 70 | 71 | self._check_wsgi_install_content(stdout) 72 | 73 | # Live test run the scripts and see that they respond to wsgi 74 | # requests. 75 | for cmd_name in self.cmd_names: 76 | self._test_wsgi(cmd_name, b'Hello World') 77 | 78 | def _test_wsgi(self, cmd_name, output, extra_args=None): 79 | cmd = os.path.join(self.temp_dir, 'bin', cmd_name) 80 | print("Running %s -p 0 -b 127.0.0.1" % cmd) 81 | popen_cmd = [cmd, '-p', '0', '-b', '127.0.0.1'] 82 | if extra_args: 83 | popen_cmd.extend(extra_args) 84 | 85 | env = {'PYTHONPATH': self._get_path()} 86 | 87 | p = subprocess.Popen(popen_cmd, stdout=subprocess.PIPE, 88 | stderr=subprocess.PIPE, cwd=self.temp_dir, 89 | env=env) 90 | self.addCleanup(p.kill) 91 | 92 | stdoutdata = p.stdout.readline() # ****... 93 | 94 | stdoutdata = p.stdout.readline() # STARTING test server... 95 | self.assertIn( 96 | b"STARTING test server pbr_testpackage.wsgi", 97 | stdoutdata) 98 | 99 | stdoutdata = p.stdout.readline() # Available at ... 100 | print(stdoutdata) 101 | m = re.search(br'(http://[^:]+:\d+)/', stdoutdata) 102 | self.assertIsNotNone(m, "Regex failed to match on %s" % stdoutdata) 103 | 104 | stdoutdata = p.stdout.readline() # DANGER! ... 105 | self.assertIn( 106 | b"DANGER! For testing only, do not use in production", 107 | stdoutdata) 108 | 109 | stdoutdata = p.stdout.readline() # ***... 110 | 111 | f = urlopen(m.group(1).decode('utf-8')) 112 | self.assertEqual(output, f.read()) 113 | 114 | # Request again so that the application can force stderr.flush(), 115 | # otherwise the log is buffered and the next readline() will hang. 116 | urlopen(m.group(1).decode('utf-8')) 117 | 118 | stdoutdata = p.stderr.readline() 119 | # we should have logged an HTTP request, return code 200, that 120 | # returned the right amount of bytes 121 | status = '"GET / HTTP/1.1" 200 %d' % len(output) 122 | self.assertIn(status.encode('utf-8'), stdoutdata) 123 | 124 | def _check_wsgi_install_content(self, install_stdout): 125 | for cmd_name in self.cmd_names: 126 | install_txt = 'Installing %s script to %s' % (cmd_name, 127 | self.temp_dir) 128 | self.assertIn(install_txt, install_stdout) 129 | 130 | cmd_filename = os.path.join(self.temp_dir, 'bin', cmd_name) 131 | 132 | script_txt = open(cmd_filename, 'r').read() 133 | self.assertNotIn('pkg_resources', script_txt) 134 | 135 | main_block = """if __name__ == "__main__": 136 | import argparse 137 | import socket 138 | import sys 139 | import wsgiref.simple_server as wss""" 140 | 141 | if cmd_name == 'pbr_test_wsgi': 142 | app_name = "main" 143 | else: 144 | app_name = "WSGI.app" 145 | 146 | starting_block = ("STARTING test server pbr_testpackage.wsgi." 147 | "%s" % app_name) 148 | 149 | else_block = """else: 150 | application = None""" 151 | 152 | self.assertIn(main_block, script_txt) 153 | self.assertIn(starting_block, script_txt) 154 | self.assertIn(else_block, script_txt) 155 | 156 | def test_with_argument(self): 157 | if os.name == 'nt': 158 | self.skipTest('Windows support is passthrough') 159 | 160 | stdout, _, return_code = self.run_setup( 161 | 'install', '--prefix=%s' % self.temp_dir) 162 | 163 | self._test_wsgi('pbr_test_wsgi', b'Foo Bar', ["--", "-c", "Foo Bar"]) 164 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/CHANGES.txt: -------------------------------------------------------------------------------- 1 | Changelog 2 | =========== 3 | 4 | 0.3 (unreleased) 5 | ------------------ 6 | 7 | - The ``glob_data_files`` hook became a pre-command hook for the install_data 8 | command instead of being a setup-hook. This is to support the additional 9 | functionality of requiring data_files with relative destination paths to be 10 | install relative to the package's install path (i.e. site-packages). 11 | 12 | - Dropped support for and deprecated the easier_install custom command. 13 | Although it should still work, it probably won't be used anymore for 14 | stsci_python packages. 15 | 16 | - Added support for the ``build_optional_ext`` command, which replaces/extends 17 | the default ``build_ext`` command. See the README for more details. 18 | 19 | - Added the ``tag_svn_revision`` setup_hook as a replacement for the 20 | setuptools-specific tag_svn_revision option to the egg_info command. This 21 | new hook is easier to use than the old tag_svn_revision option: It's 22 | automatically enabled by the presence of ``.dev`` in the version string, and 23 | disabled otherwise. 24 | 25 | - The ``svn_info_pre_hook`` and ``svn_info_post_hook`` have been replaced with 26 | ``version_pre_command_hook`` and ``version_post_command_hook`` respectively. 27 | However, a new ``version_setup_hook``, which has the same purpose, has been 28 | added. It is generally easier to use and will give more consistent results 29 | in that it will run every time setup.py is run, regardless of which command 30 | is used. ``stsci.distutils`` itself uses this hook--see the `setup.cfg` file 31 | and `stsci/distutils/__init__.py` for example usage. 32 | 33 | - Instead of creating an `svninfo.py` module, the new ``version_`` hooks create 34 | a file called `version.py`. In addition to the SVN info that was included 35 | in `svninfo.py`, it includes a ``__version__`` variable to be used by the 36 | package's `__init__.py`. This allows there to be a hard-coded 37 | ``__version__`` variable included in the source code, rather than using 38 | pkg_resources to get the version. 39 | 40 | - In `version.py`, the variables previously named ``__svn_version__`` and 41 | ``__full_svn_info__`` are now named ``__svn_revision__`` and 42 | ``__svn_full_info__``. 43 | 44 | - Fixed a bug when using stsci.distutils in the installation of other packages 45 | in the ``stsci.*`` namespace package. If stsci.distutils was not already 46 | installed, and was downloaded automatically by distribute through the 47 | setup_requires option, then ``stsci.distutils`` would fail to import. This 48 | is because the way the namespace package (nspkg) mechanism currently works, 49 | all packages belonging to the nspkg *must* be on the import path at initial 50 | import time. 51 | 52 | So when installing stsci.tools, for example, if ``stsci.tools`` is imported 53 | from within the source code at install time, but before ``stsci.distutils`` 54 | is downloaded and added to the path, the ``stsci`` package is already 55 | imported and can't be extended to include the path of ``stsci.distutils`` 56 | after the fact. The easiest way of dealing with this, it seems, is to 57 | delete ``stsci`` from ``sys.modules``, which forces it to be reimported, now 58 | the its ``__path__`` extended to include ``stsci.distutil``'s path. 59 | 60 | 61 | 0.2.2 (2011-11-09) 62 | ------------------ 63 | 64 | - Fixed check for the issue205 bug on actual setuptools installs; before it 65 | only worked on distribute. setuptools has the issue205 bug prior to version 66 | 0.6c10. 67 | 68 | - Improved the fix for the issue205 bug, especially on setuptools. 69 | setuptools, prior to 0.6c10, did not back of sys.modules either before 70 | sandboxing, which causes serious problems. In fact, it's so bad that it's 71 | not enough to add a sys.modules backup to the current sandbox: It's in fact 72 | necessary to monkeypatch setuptools.sandbox.run_setup so that any subsequent 73 | calls to it also back up sys.modules. 74 | 75 | 76 | 0.2.1 (2011-09-02) 77 | ------------------ 78 | 79 | - Fixed the dependencies so that setuptools is requirement but 'distribute' 80 | specifically. Previously installation could fail if users had plain 81 | setuptools installed and not distribute 82 | 83 | 0.2 (2011-08-23) 84 | ------------------ 85 | 86 | - Initial public release 87 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (C) 2005 Association of Universities for Research in Astronomy (AURA) 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are met: 5 | 6 | 1. Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | 9 | 2. Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following 11 | disclaimer in the documentation and/or other materials provided 12 | with the distribution. 13 | 14 | 3. The name of AURA and its representatives may not be used to 15 | endorse or promote products derived from this software without 16 | specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 19 | WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 20 | MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 22 | INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 23 | BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 24 | OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 25 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR 26 | TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 27 | USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH 28 | DAMAGE. 29 | 30 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/MANIFEST.in: -------------------------------------------------------------------------------- 1 | include data_files/* 2 | exclude pbr_testpackage/extra.py 3 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/README.txt: -------------------------------------------------------------------------------- 1 | Introduction 2 | ============ 3 | This package contains utilities used to package some of STScI's Python 4 | projects; specifically those projects that comprise stsci_python_ and 5 | Astrolib_. 6 | 7 | It currently consists mostly of some setup_hook scripts meant for use with 8 | `distutils2/packaging`_ and/or pbr_, and a customized easy_install command 9 | meant for use with distribute_. 10 | 11 | This package is not meant for general consumption, though it might be worth 12 | looking at for examples of how to do certain things with your own packages, but 13 | YMMV. 14 | 15 | Features 16 | ======== 17 | 18 | Hook Scripts 19 | ------------ 20 | Currently the main features of this package are a couple of setup_hook scripts. 21 | In distutils2, a setup_hook is a script that runs at the beginning of any 22 | pysetup command, and can modify the package configuration read from setup.cfg. 23 | There are also pre- and post-command hooks that only run before/after a 24 | specific setup command (eg. build_ext, install) is run. 25 | 26 | stsci.distutils.hooks.use_packages_root 27 | ''''''''''''''''''''''''''''''''''''''' 28 | If using the ``packages_root`` option under the ``[files]`` section of 29 | setup.cfg, this hook will add that path to ``sys.path`` so that modules in your 30 | package can be imported and used in setup. This can be used even if 31 | ``packages_root`` is not specified--in this case it adds ``''`` to 32 | ``sys.path``. 33 | 34 | stsci.distutils.hooks.version_setup_hook 35 | '''''''''''''''''''''''''''''''''''''''' 36 | Creates a Python module called version.py which currently contains four 37 | variables: 38 | 39 | * ``__version__`` (the release version) 40 | * ``__svn_revision__`` (the SVN revision info as returned by the ``svnversion`` 41 | command) 42 | * ``__svn_full_info__`` (as returned by the ``svn info`` command) 43 | * ``__setup_datetime__`` (the date and time that setup.py was last run). 44 | 45 | These variables can be imported in the package's `__init__.py` for degugging 46 | purposes. The version.py module will *only* be created in a package that 47 | imports from the version module in its `__init__.py`. It should be noted that 48 | this is generally preferable to writing these variables directly into 49 | `__init__.py`, since this provides more control and is less likely to 50 | unexpectedly break things in `__init__.py`. 51 | 52 | stsci.distutils.hooks.version_pre_command_hook 53 | '''''''''''''''''''''''''''''''''''''''''''''' 54 | Identical to version_setup_hook, but designed to be used as a pre-command 55 | hook. 56 | 57 | stsci.distutils.hooks.version_post_command_hook 58 | ''''''''''''''''''''''''''''''''''''''''''''''' 59 | The complement to version_pre_command_hook. This will delete any version.py 60 | files created during a build in order to prevent them from cluttering an SVN 61 | working copy (note, however, that version.py is *not* deleted from the build/ 62 | directory, so a copy of it is still preserved). It will also not be deleted 63 | if the current directory is not an SVN working copy. For example, if source 64 | code extracted from a source tarball it will be preserved. 65 | 66 | stsci.distutils.hooks.tag_svn_revision 67 | '''''''''''''''''''''''''''''''''''''' 68 | A setup_hook to add the SVN revision of the current working copy path to the 69 | package version string, but only if the version ends in .dev. 70 | 71 | For example, ``mypackage-1.0.dev`` becomes ``mypackage-1.0.dev1234``. This is 72 | in accordance with the version string format standardized by PEP 386. 73 | 74 | This should be used as a replacement for the ``tag_svn_revision`` option to 75 | the egg_info command. This hook is more compatible with packaging/distutils2, 76 | which does not include any VCS support. This hook is also more flexible in 77 | that it turns the revision number on/off depending on the presence of ``.dev`` 78 | in the version string, so that it's not automatically added to the version in 79 | final releases. 80 | 81 | This hook does require the ``svnversion`` command to be available in order to 82 | work. It does not examine the working copy metadata directly. 83 | 84 | stsci.distutils.hooks.numpy_extension_hook 85 | '''''''''''''''''''''''''''''''''''''''''' 86 | This is a pre-command hook for the build_ext command. To use it, add a 87 | ``[build_ext]`` section to your setup.cfg, and add to it:: 88 | 89 | pre-hook.numpy-extension-hook = stsci.distutils.hooks.numpy_extension_hook 90 | 91 | This hook must be used to build extension modules that use Numpy. The primary 92 | side-effect of this hook is to add the correct numpy include directories to 93 | `include_dirs`. To use it, add 'numpy' to the 'include-dirs' option of each 94 | extension module that requires numpy to build. The value 'numpy' will be 95 | replaced with the actual path to the numpy includes. 96 | 97 | stsci.distutils.hooks.is_display_option 98 | ''''''''''''''''''''''''''''''''''''''' 99 | This is not actually a hook, but is a useful utility function that can be used 100 | in writing other hooks. Basically, it returns ``True`` if setup.py was run 101 | with a "display option" such as --version or --help. This can be used to 102 | prevent your hook from running in such cases. 103 | 104 | stsci.distutils.hooks.glob_data_files 105 | ''''''''''''''''''''''''''''''''''''' 106 | A pre-command hook for the install_data command. Allows filename wildcards as 107 | understood by ``glob.glob()`` to be used in the data_files option. This hook 108 | must be used in order to have this functionality since it does not normally 109 | exist in distutils. 110 | 111 | This hook also ensures that data files are installed relative to the package 112 | path. data_files shouldn't normally be installed this way, but the 113 | functionality is required for a few special cases. 114 | 115 | 116 | Commands 117 | -------- 118 | build_optional_ext 119 | '''''''''''''''''' 120 | This serves as an optional replacement for the default built_ext command, 121 | which compiles C extension modules. Its purpose is to allow extension modules 122 | to be *optional*, so that if their build fails the rest of the package is 123 | still allowed to be built and installed. This can be used when an extension 124 | module is not definitely required to use the package. 125 | 126 | To use this custom command, add:: 127 | 128 | commands = stsci.distutils.command.build_optional_ext.build_optional_ext 129 | 130 | under the ``[global]`` section of your package's setup.cfg. Then, to mark 131 | an individual extension module as optional, under the setup.cfg section for 132 | that extension add:: 133 | 134 | optional = True 135 | 136 | Optionally, you may also add a custom failure message by adding:: 137 | 138 | fail_message = The foobar extension module failed to compile. 139 | This could be because you lack such and such headers. 140 | This package will still work, but such and such features 141 | will be disabled. 142 | 143 | 144 | .. _stsci_python: http://www.stsci.edu/resources/software_hardware/pyraf/stsci_python 145 | .. _Astrolib: http://www.scipy.org/AstroLib/ 146 | .. _distutils2/packaging: http://distutils2.notmyidea.org/ 147 | .. _d2to1: http://pypi.python.org/pypi/d2to1 148 | .. _distribute: http://pypi.python.org/pypi/distribute 149 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/data_files/a.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/tests/testpackage/data_files/a.txt -------------------------------------------------------------------------------- /pbr/tests/testpackage/data_files/b.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/tests/testpackage/data_files/b.txt -------------------------------------------------------------------------------- /pbr/tests/testpackage/data_files/c.rst: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/tests/testpackage/data_files/c.rst -------------------------------------------------------------------------------- /pbr/tests/testpackage/doc/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 11 | # implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | 16 | # -- General configuration ---------------------------------------------------- 17 | 18 | # Add any Sphinx extension module names here, as strings. They can be 19 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom ones. 20 | extensions = [ 21 | 'sphinx.ext.autodoc', 22 | ] 23 | 24 | # autodoc generation is a bit aggressive and a nuisance when doing heavy 25 | # text edit cycles. 26 | # execute "export SPHINX_DEBUG=1" in your terminal to disable 27 | 28 | # The suffix of source filenames. 29 | source_suffix = '.rst' 30 | 31 | # The master toctree document. 32 | master_doc = 'index' 33 | 34 | # General information about the project. 35 | project = u'testpackage' 36 | copyright = u'2013, OpenStack Foundation' 37 | 38 | # If true, '()' will be appended to :func: etc. cross-reference text. 39 | add_function_parentheses = True 40 | 41 | # If true, the current module name will be prepended to all description 42 | # unit titles (such as .. function::). 43 | add_module_names = True 44 | 45 | # The name of the Pygments (syntax highlighting) style to use. 46 | pygments_style = 'sphinx' 47 | 48 | 49 | # -- Options for HTML output -------------------------------------------------- 50 | 51 | # Grouping the document tree into LaTeX files. List of tuples 52 | # (source start file, target name, title, author, documentclass 53 | # [howto/manual]). 54 | latex_documents = [ 55 | ('index', 56 | '%s.tex' % project, 57 | u'%s Documentation' % project, 58 | u'OpenStack Foundation', 'manual'), 59 | ] 60 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/doc/source/index.rst: -------------------------------------------------------------------------------- 1 | .. testpackage documentation master file, created by 2 | sphinx-quickstart on Tue Jul 9 22:26:36 2013. 3 | You can adapt this file completely to your liking, but it should at least 4 | contain the root `toctree` directive. 5 | 6 | Welcome to testpackage's documentation! 7 | ======================================================== 8 | 9 | Contents: 10 | 11 | .. toctree:: 12 | :maxdepth: 2 13 | 14 | installation 15 | usage 16 | 17 | Indices and tables 18 | ================== 19 | 20 | * :ref:`genindex` 21 | * :ref:`modindex` 22 | * :ref:`search` 23 | 24 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/doc/source/installation.rst: -------------------------------------------------------------------------------- 1 | ============ 2 | Installation 3 | ============ 4 | 5 | At the command line:: 6 | 7 | $ pip install testpackage 8 | 9 | Or, if you have virtualenvwrapper installed:: 10 | 11 | $ mkvirtualenv testpackage 12 | $ pip install testpackage 13 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/doc/source/usage.rst: -------------------------------------------------------------------------------- 1 | ======== 2 | Usage 3 | ======== 4 | 5 | To use testpackage in a project:: 6 | 7 | import testpackage 8 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/extra-file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/tests/testpackage/extra-file.txt -------------------------------------------------------------------------------- /pbr/tests/testpackage/git-extra-file.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/tests/testpackage/git-extra-file.txt -------------------------------------------------------------------------------- /pbr/tests/testpackage/pbr_testpackage/__init__.py: -------------------------------------------------------------------------------- 1 | import pbr.version 2 | 3 | __version__ = pbr.version.VersionInfo('pbr_testpackage').version_string() 4 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/pbr_testpackage/_setup_hooks.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Copyright (C) 2013 Association of Universities for Research in Astronomy 17 | # (AURA) 18 | # 19 | # Redistribution and use in source and binary forms, with or without 20 | # modification, are permitted provided that the following conditions are met: 21 | # 22 | # 1. Redistributions of source code must retain the above copyright 23 | # notice, this list of conditions and the following disclaimer. 24 | # 25 | # 2. Redistributions in binary form must reproduce the above 26 | # copyright notice, this list of conditions and the following 27 | # disclaimer in the documentation and/or other materials provided 28 | # with the distribution. 29 | # 30 | # 3. The name of AURA and its representatives may not be used to 31 | # endorse or promote products derived from this software without 32 | # specific prior written permission. 33 | # 34 | # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 35 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 36 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37 | # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 38 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 39 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 40 | 41 | from distutils.command import build_py 42 | 43 | 44 | def test_hook_1(config): 45 | print('test_hook_1') 46 | 47 | 48 | def test_hook_2(config): 49 | print('test_hook_2') 50 | 51 | 52 | class test_command(build_py.build_py): 53 | command_name = 'build_py' 54 | 55 | def run(self): 56 | print('Running custom build_py command.') 57 | return build_py.build_py.run(self) 58 | 59 | 60 | def test_pre_hook(cmdobj): 61 | print('build_ext pre-hook') 62 | 63 | 64 | def test_post_hook(cmdobj): 65 | print('build_ext post-hook') 66 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/pbr_testpackage/cmd.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | from __future__ import print_function 16 | 17 | 18 | def main(): 19 | print("PBR Test Command") 20 | 21 | 22 | class Foo(object): 23 | 24 | @classmethod 25 | def bar(self): 26 | print("PBR Test Command - with class!") 27 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/pbr_testpackage/extra.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/tests/testpackage/pbr_testpackage/extra.py -------------------------------------------------------------------------------- /pbr/tests/testpackage/pbr_testpackage/package_data/1.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/tests/testpackage/pbr_testpackage/package_data/1.txt -------------------------------------------------------------------------------- /pbr/tests/testpackage/pbr_testpackage/package_data/2.txt: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openstack/pbr/5d4a1815afa920cf20e889be20617105446f7ce2/pbr/tests/testpackage/pbr_testpackage/package_data/2.txt -------------------------------------------------------------------------------- /pbr/tests/testpackage/pbr_testpackage/wsgi.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | from __future__ import print_function 16 | 17 | import argparse 18 | import functools 19 | import sys 20 | 21 | 22 | def application(env, start_response, data): 23 | sys.stderr.flush() # Force the previous request log to be written. 24 | start_response('200 OK', [('Content-Type', 'text/html')]) 25 | return [data.encode('utf-8')] 26 | 27 | 28 | def main(): 29 | parser = argparse.ArgumentParser(description='Return a string.') 30 | parser.add_argument('--content', '-c', help='String returned', 31 | default='Hello World') 32 | args = parser.parse_args() 33 | return functools.partial(application, data=args.content) 34 | 35 | 36 | class WSGI(object): 37 | 38 | @classmethod 39 | def app(self): 40 | return functools.partial(application, data='Hello World') 41 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = pbr_testpackage 3 | # TODO(lifeless) we should inject this as needed otherwise we're not truely 4 | # testing postversioned codepaths. 5 | version = 0.1.dev 6 | author = OpenStack 7 | author_email = openstack-discuss@lists.openstack.org 8 | home_page = http://pypi.python.org/pypi/pbr 9 | project_urls = 10 | Bug Tracker = https://bugs.launchpad.net/pbr/ 11 | Documentation = https://docs.openstack.org/pbr/ 12 | Source Code = https://opendev.org/openstack/pbr 13 | summary = Test package for testing pbr 14 | description_file = 15 | README.txt 16 | CHANGES.txt 17 | description_content_type = text/plain; charset=UTF-8 18 | python_requires = >=2.5 19 | 20 | requires_dist = 21 | setuptools 22 | 23 | classifier = 24 | Development Status :: 3 - Alpha 25 | Intended Audience :: Developers 26 | License :: OSI Approved :: BSD License 27 | Programming Language :: Python 28 | Topic :: Scientific/Engineering 29 | Topic :: Software Development :: Build Tools 30 | Topic :: Software Development :: Libraries :: Python Modules 31 | Topic :: System :: Archiving :: Packaging 32 | 33 | keywords = packaging, distutils, setuptools 34 | 35 | [files] 36 | packages = pbr_testpackage 37 | package_data = testpackage = package_data/*.txt 38 | data_files = testpackage/data_files = data_files/* 39 | extra_files = extra-file.txt 40 | 41 | [entry_points] 42 | console_scripts = 43 | pbr_test_cmd = pbr_testpackage.cmd:main 44 | pbr_test_cmd_with_class = pbr_testpackage.cmd:Foo.bar 45 | 46 | wsgi_scripts = 47 | pbr_test_wsgi = pbr_testpackage.wsgi:main 48 | pbr_test_wsgi_with_class = pbr_testpackage.wsgi:WSGI.app 49 | 50 | [extension=pbr_testpackage.testext] 51 | sources = src/testext.c 52 | optional = True 53 | 54 | [global] 55 | #setup_hooks = 56 | # pbr_testpackage._setup_hooks.test_hook_1 57 | # pbr_testpackage._setup_hooks.test_hook_2 58 | commands = pbr_testpackage._setup_hooks.test_command 59 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import setuptools 17 | 18 | setuptools.setup( 19 | setup_requires=['pbr'], 20 | pbr=True, 21 | ) 22 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/src/testext.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | 4 | static PyMethodDef TestextMethods[] = { 5 | {NULL, NULL, 0, NULL} 6 | }; 7 | 8 | 9 | #if PY_MAJOR_VERSION >=3 10 | static struct PyModuleDef testextmodule = { 11 | PyModuleDef_HEAD_INIT, /* This should correspond to a PyModuleDef_Base type */ 12 | "testext", /* This is the module name */ 13 | "Test extension module", /* This is the module docstring */ 14 | -1, /* This defines the size of the module and says everything is global */ 15 | TestextMethods /* This is the method definition */ 16 | }; 17 | 18 | PyObject* 19 | PyInit_testext(void) 20 | { 21 | return PyModule_Create(&testextmodule); 22 | } 23 | #else 24 | PyMODINIT_FUNC 25 | inittestext(void) 26 | { 27 | Py_InitModule("testext", TestextMethods); 28 | } 29 | #endif 30 | -------------------------------------------------------------------------------- /pbr/tests/testpackage/test-requirements.txt: -------------------------------------------------------------------------------- 1 | ordereddict;python_version=='2.6' 2 | requests-mock 3 | -------------------------------------------------------------------------------- /pbr/tests/util.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | # 16 | # Copyright (C) 2013 Association of Universities for Research in Astronomy 17 | # (AURA) 18 | # 19 | # Redistribution and use in source and binary forms, with or without 20 | # modification, are permitted provided that the following conditions are met: 21 | # 22 | # 1. Redistributions of source code must retain the above copyright 23 | # notice, this list of conditions and the following disclaimer. 24 | # 25 | # 2. Redistributions in binary form must reproduce the above 26 | # copyright notice, this list of conditions and the following 27 | # disclaimer in the documentation and/or other materials provided 28 | # with the distribution. 29 | # 30 | # 3. The name of AURA and its representatives may not be used to 31 | # endorse or promote products derived from this software without 32 | # specific prior written permission. 33 | # 34 | # THIS SOFTWARE IS PROVIDED BY AURA ``AS IS'' AND ANY EXPRESS OR IMPLIED 35 | # WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF 36 | # MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 37 | # DISCLAIMED. IN NO EVENT SHALL AURA BE LIABLE FOR ANY DIRECT, INDIRECT, 38 | # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 39 | # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS 40 | 41 | import contextlib 42 | import os 43 | import shutil 44 | import stat 45 | import sys 46 | 47 | try: 48 | import ConfigParser as configparser 49 | except ImportError: 50 | import configparser 51 | 52 | 53 | @contextlib.contextmanager 54 | def open_config(filename): 55 | if sys.version_info >= (3, 2): 56 | cfg = configparser.ConfigParser() 57 | else: 58 | cfg = configparser.SafeConfigParser() 59 | cfg.read(filename) 60 | yield cfg 61 | with open(filename, 'w') as fp: 62 | cfg.write(fp) 63 | 64 | 65 | def rmtree(path): 66 | """shutil.rmtree() with error handler. 67 | 68 | Handle 'access denied' from trying to delete read-only files. 69 | """ 70 | 71 | def onerror(func, path, exc_info): 72 | if not os.access(path, os.W_OK): 73 | os.chmod(path, stat.S_IWUSR) 74 | func(path) 75 | else: 76 | raise 77 | 78 | return shutil.rmtree(path, onerror=onerror) 79 | -------------------------------------------------------------------------------- /playbooks/pbr-installation-openstack/pre.yaml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | roles: 3 | - ensure-pip 4 | - ensure-virtualenv 5 | -------------------------------------------------------------------------------- /playbooks/pbr-installation-openstack/run.yaml: -------------------------------------------------------------------------------- 1 | - hosts: all 2 | tasks: 3 | - shell: 4 | cmd: | 5 | export PBR_PIP_VERSION="{{ pbr_pip_version }}" 6 | bash -xe /home/zuul/src/opendev.org/openstack/pbr/tools/integration.sh $(cat /home/zuul/src/opendev.org/openstack/requirements/projects.txt) 7 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | # Includes pep660 support in setuptools 4 | "setuptools>=64.0.0;python_version>='3.7'", 5 | # Fallback to whatever we can get otherwise. 6 | # Note this is not something projects should typically 7 | # need. PBR attempts to support a wide range of python 8 | # versions and this is an exceptional case due to that 9 | # need. 10 | "setuptools;python_version<'3.7'" 11 | ] 12 | build-backend = "pbr.build" 13 | backend-path = ["."] 14 | 15 | [tools.setuptools] 16 | py-modules=[] 17 | -------------------------------------------------------------------------------- /releasenotes/notes/bdist_wininst-removal-4a1c7c3a9f08238d.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | other: 3 | - | 4 | Support to generate bdist_wininst packages has been removed. As of Python 5 | 3.8 and Setuptools 47.2 it's deprecated in favor of just using wheels for 6 | Windows platform packaging. See 7 | https://discuss.python.org/t/deprecate-bdist-wininst/ and 8 | https://discuss.python.org/t/remove-distutils-bdist-wininst-command/ for 9 | more details. 10 | -------------------------------------------------------------------------------- /releasenotes/notes/build_sphinx_removal-de990a5c14a9e64d.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | The deprecated support for `setup.py build_sphinx` has been removed. 5 | This feature has been deprecated since version 4.2. Setuptools and 6 | sphinx have removed support for this command which breaks PBR's ability 7 | to integrate with those two tools to provide this functionality. 8 | Users should switch to building sphinx docs with sphinx directly. 9 | -------------------------------------------------------------------------------- /releasenotes/notes/cmd-e6664dcbd42d3935.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Add an option to print only the version of a package 5 | 6 | Example: 7 | 8 | .. code-block:: bash 9 | 10 | $ pbr info -s pkgname 11 | 1.2.3 12 | -------------------------------------------------------------------------------- /releasenotes/notes/deprecate-pyN-requirements-364655c38fa5b780.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | deprecations: 3 | - | 4 | Support for ``pyN``-suffixed requirement files has been deprecated: 5 | environment markers should be used instead. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/deprecate-testr-nose-integration-56e3e11248d946fc.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | deprecations: 3 | - | 4 | *testr* and *nose* integration has been deprecated. This feature allowed 5 | *pbr* to dynamically configure the test runner used when running 6 | ``setup.py test``. However, this target has fallen out of favour in both 7 | the OpenStack and broader Python ecosystem, and both *testr* and *nose* 8 | offer native setuptools commands that can be manually aliased to ``test`` 9 | on a per-project basis, if necessary. This feature will be removed in a 10 | future release. 11 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-global-replace-of-src-prefix-in-glob-eb850b94ca96993e.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixes a bug where the directory names of items specified in ``data_files`` 5 | could be renamed if the source prefix glob was contained within the 6 | directory name. See `bug 1810804 7 | `_ for details. For more 8 | information on ``data_files``, see the `distutils documentation 9 | `_. 10 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-handling-of-spaces-in-data-files-glob-0fe0c398d70dfea8.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fixes the handling of spaces in data_files globs. Please see `bug 1810934 5 | `_ for more details. 6 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-keywords-as-cfg-list-6cadc5141429d7f5.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fix error when ``keywords`` argument as a cfg list. Previously ``keywords`` 5 | were ``CSV_FIELDS`` and with these changes ``keywords`` are now 6 | ``MULTI_FIELDS``. Refer to https://bugs.launchpad.net/pbr/+bug/1811475 7 | for more information. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-mapping-value-explode-with-equal-sign-41bf822fa4dd0e68.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fix mapping error on values who contains a literal ``=``. Example when 5 | setup.cfg contains content like the following project urls configuration 6 | "project_urls = Documentation = http://foo.bar/?badge=latest". 7 | https://bugs.launchpad.net/pbr/+bug/1817592 8 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-pep517-metadata-regression-bc287e60e45b2732.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Packages generated with the 5.8.0 release of PBR failed to incorporate 5 | pbr.json metadata files. This is now corrected. Users can rebuild packages 6 | with newer PBR if they want that missing metadata generated. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/fix-symbols-leading-spaces-f68928d75a8f0997.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | Fix an issue where symbols that were indented 5 | would produce an incorrect version. -------------------------------------------------------------------------------- /releasenotes/notes/ignore-find-links-07cf54f465aa33a6.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | PBR now ignores ``--find-links`` in requirements files. This option is not 5 | a valid ``install_requires`` entry for setuptools and thus breaks 6 | PBR-based installs. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/long-descr-content-type-f9a1003acbb8740f.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | fixes: 3 | - | 4 | The ``description-content-type`` was not being set correctly. It 5 | will now be correctly populated when using ``setuptools`` 39.2.0 6 | and beyond. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/pep517-support-89189ce0bab15845.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | PBR now includes a PEP 517 build-backend and can be used in 5 | pyproject.toml build-system configuration. Setuptools continues 6 | to be the underlying mechanism with PBR acting as a driver via 7 | PEP 517 entrypoints. 8 | -------------------------------------------------------------------------------- /releasenotes/notes/remove-command-hooks-907d9c2325f306ca.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | upgrade: 3 | - | 4 | Support for entry point command hooks has been removed. This feature was 5 | poorly tested, poorly documented, and broken in some environments. 6 | Support for global hooks is not affected. 7 | -------------------------------------------------------------------------------- /releasenotes/notes/support-vcs-uris-with-subdirectories-20ad68b6138f72ca.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Subdirectories can now be included when specfifying a requirement in 5 | ``requirements.txt`` or ``test-requirements.txt`` using VCS URIs. For 6 | example: 7 | 8 | -e git+https://foo.com/zipball#egg=bar&subdirectory=baz 9 | 10 | For more information, refer to the `pip documentation 11 | `__. 12 | -------------------------------------------------------------------------------- /releasenotes/notes/use_2to3-removal-ac48bf9fbfa049b1.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | other: 3 | - | 4 | The 2to3 conversion utility has been long discouraged in favor of writing 5 | multi-version-capable scripts. As of Setuptools 46.2.0 it's deprecated and 6 | slated for removal from the Python 3.10 standard library. Projects which 7 | still need it are encouraged to perform conversion prior to packaging. See 8 | https://bugs.python.org/issue40360 and 9 | https://github.com/pypa/setuptools/issues/2086 for more details. 10 | -------------------------------------------------------------------------------- /releasenotes/notes/v_version-457b38c8679c5868.yaml: -------------------------------------------------------------------------------- 1 | --- 2 | features: 3 | - | 4 | Support version parsing of git tag with the ``v`` pattern 5 | (or ``V``), in addition to ````. 6 | -------------------------------------------------------------------------------- /releasenotes/source/conf.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | # Licensed under the Apache License, Version 2.0 (the "License"); 3 | # you may not use this file except in compliance with the License. 4 | # You may obtain a copy of the License at 5 | # 6 | # http://www.apache.org/licenses/LICENSE-2.0 7 | # 8 | # Unless required by applicable law or agreed to in writing, software 9 | # distributed under the License is distributed on an "AS IS" BASIS, 10 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 11 | # implied. 12 | # See the License for the specific language governing permissions and 13 | # limitations under the License. 14 | 15 | # pbr Release Notes documentation build configuration file 16 | 17 | 18 | # -- General configuration ------------------------------------------------ 19 | 20 | # Add any Sphinx extension module names here, as strings. They can be 21 | # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom 22 | # ones. 23 | extensions = [ 24 | 'openstackdocstheme', 25 | 'reno.sphinxext', 26 | ] 27 | 28 | # The master toctree document. 29 | master_doc = 'index' 30 | 31 | # Release notes are version independent 32 | # The short X.Y version. 33 | version = '' 34 | # The full version, including alpha/beta/rc tags. 35 | release = '' 36 | 37 | 38 | # -- Options for HTML output ---------------------------------------------- 39 | 40 | # The theme to use for HTML and HTML Help pages. See the documentation for 41 | # a list of builtin themes. 42 | html_theme = 'openstackdocs' 43 | 44 | # -- Options for openstackdocstheme --------------------------------------- 45 | 46 | # Deprecated options for openstackdocstheme < 2.2.0, can be removed once 47 | # pbr stops supporting py27. 48 | repository_name = 'openstack/pbr' 49 | bug_project = 'pbr' 50 | bug_tag = '' 51 | 52 | # New options with openstackdocstheme >=2.2.0 53 | openstackdocs_repo_name = 'openstack/pbr' 54 | openstackdocs_auto_name = False 55 | openstackdocs_bug_project = 'pbr' 56 | openstackdocs_bug_tag = '' 57 | -------------------------------------------------------------------------------- /releasenotes/source/index.rst: -------------------------------------------------------------------------------- 1 | ================= 2 | pbr Release Notes 3 | ================= 4 | 5 | .. toctree:: 6 | :maxdepth: 1 7 | 8 | unreleased 9 | -------------------------------------------------------------------------------- /releasenotes/source/unreleased.rst: -------------------------------------------------------------------------------- 1 | ============================ 2 | Current Series Release Notes 3 | ============================ 4 | 5 | .. release-notes:: 6 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | # PBR always implicitly depended on setuptools which until python3.12 2 | # was included by default in python installations. Since python3.12 3 | # setuptools is not included so we add an explicit dependency on 4 | # setuptools here. For the sake of simplicity we don't set an 5 | # environment marker restricting this to specific Python versions, 6 | # since in older environments it should just be a no-op anyway. 7 | # 8 | # DO NOT add any other dependencies as PBR is meant to be minimalist 9 | # to avoid problems with bootstrapping build environments. 10 | 11 | setuptools 12 | -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | name = pbr 3 | author = OpenStack 4 | author_email = openstack-discuss@lists.openstack.org 5 | summary = Python Build Reasonableness 6 | long_description = file: README.rst 7 | long_description_content_type = text/x-rst; charset=UTF-8 8 | url = https://docs.openstack.org/pbr/latest/ 9 | project_urls = 10 | Bug Tracker = https://bugs.launchpad.net/pbr/ 11 | Documentation = https://docs.openstack.org/pbr/ 12 | Source Code = https://opendev.org/openstack/pbr 13 | classifiers = 14 | Development Status :: 5 - Production/Stable 15 | Environment :: Console 16 | Environment :: OpenStack 17 | Intended Audience :: Developers 18 | Intended Audience :: Information Technology 19 | License :: OSI Approved :: Apache Software License 20 | Operating System :: OS Independent 21 | Programming Language :: Python 22 | Programming Language :: Python :: 2 23 | Programming Language :: Python :: 2.7 24 | Programming Language :: Python :: 3 25 | Programming Language :: Python :: 3.5 26 | Programming Language :: Python :: 3.6 27 | Programming Language :: Python :: 3.7 28 | Programming Language :: Python :: 3.8 29 | Programming Language :: Python :: 3.9 30 | Programming Language :: Python :: 3.10 31 | Programming Language :: Python :: 3.11 32 | 33 | [options] 34 | python_requires = >=2.6 35 | 36 | [files] 37 | packages = 38 | pbr 39 | 40 | [entry_points] 41 | distutils.setup_keywords = 42 | pbr = pbr.core:pbr 43 | egg_info.writers = 44 | pbr.json = pbr.pbr_json:write_pbr_json 45 | console_scripts = 46 | pbr = pbr.cmd.main:main 47 | 48 | [bdist_wheel] 49 | universal = 1 50 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | # Copyright (c) 2013 Hewlett-Packard Development Company, L.P. 2 | # 3 | # Licensed under the Apache License, Version 2.0 (the "License"); 4 | # you may not use this file except in compliance with the License. 5 | # You may obtain a copy of the License at 6 | # 7 | # http://www.apache.org/licenses/LICENSE-2.0 8 | # 9 | # Unless required by applicable law or agreed to in writing, software 10 | # distributed under the License is distributed on an "AS IS" BASIS, 11 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 12 | # implied. 13 | # See the License for the specific language governing permissions and 14 | # limitations under the License. 15 | 16 | import setuptools 17 | 18 | from pbr import util 19 | 20 | setuptools.setup( 21 | **util.cfg_to_args()) 22 | -------------------------------------------------------------------------------- /test-requirements.txt: -------------------------------------------------------------------------------- 1 | # The order of packages is significant, because pip processes them in the order 2 | # of appearance. Changing the order has an impact on the overall integration 3 | # process, which may cause wedges in the gate later. 4 | 5 | wheel>=0.32.0 # MIT 6 | fixtures>=3.0.0 # Apache-2.0/BSD 7 | hacking>=1.1.0,<4.0.0;python_version>='3.6' # Apache-2.0 8 | mock>=2.0.0,<4.0.0;python_version=='2.7' # BSD 9 | stestr>=2.1.0,<3.0;python_version=='2.7' # Apache-2.0 10 | stestr>=2.1.0;python_version>='3.0' # Apache-2.0 11 | testresources>=2.0.0 # Apache-2.0/BSD 12 | testscenarios>=0.4 # Apache-2.0/BSD 13 | testtools>=2.2.0 # MIT 14 | virtualenv>=20.0.3 # MIT 15 | coverage!=4.4,>=4.0 # Apache-2.0 16 | 17 | # optionally exposed by distutils commands 18 | sphinx!=1.6.6,!=1.6.7,>=1.6.2,<2.0.0;python_version=='2.7' # BSD 19 | sphinx!=1.6.6,!=1.6.7,>=1.6.2;python_version>='3.4' # BSD 20 | testrepository>=0.0.18 # Apache-2.0/BSD 21 | 22 | pre-commit>=2.6.0;python_version>='3.6' # MIT 23 | -------------------------------------------------------------------------------- /tools/integration.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -xe 2 | # Parameters: 3 | # PBR_PIP_VERSION :- if not set, run pip's latest release, if set must be a 4 | # valid reference file entry describing what pip to use. 5 | # WHEELHOUSE :- if not set, use a temporary wheelhouse, set to a specific path 6 | # to use an existing one. 7 | # PIPFLAGS :- may be set to any pip global option for e.g. debugging. 8 | # Bootstrappping the mkenv needs to install *a* pip 9 | export PIPVERSION=pip 10 | PIPFLAGS=${PIPFLAGS:-} 11 | 12 | function mkvenv { 13 | venv=$1 14 | 15 | rm -rf $venv 16 | virtualenv -p python3 $venv 17 | $venv/bin/pip install $PIPFLAGS -U $PIPVERSION wheel 18 | 19 | # If a change to PBR is being tested, preinstall the wheel for it 20 | if [ -n "$PBR_CHANGE" ] ; then 21 | $venv/bin/pip install $PIPFLAGS $pbrsdistdir/dist/pbr-*.whl 22 | fi 23 | } 24 | 25 | # BASE should be a directory with a subdir called "openstack" and in that 26 | # dir, there should be a git repository for every entry in PROJECTS 27 | BASE=${BASE:-/home/zuul/src/opendev.org/} 28 | 29 | REPODIR=${REPODIR:-$BASE/openstack} 30 | 31 | # TODO: Figure out how to get this on to the box properly 32 | sudo apt-get update 33 | sudo apt-get install -y --force-yes libvirt-dev libxml2-dev libxslt-dev libmysqlclient-dev libpq-dev libnspr4-dev pkg-config libsqlite3-dev libffi-dev libldap2-dev libsasl2-dev ccache libkrb5-dev liberasurecode-dev libjpeg-dev libsystemd-dev libnss3-dev libssl-dev libpcre3-dev 34 | 35 | # FOR pyyaml 36 | sudo apt-get install -y --force-yes debhelper python3-all-dev python3-all-dbg libyaml-dev cython3 quilt 37 | 38 | # And use ccache explitly 39 | export PATH=/usr/lib/ccache:$PATH 40 | 41 | tmpdir=$(mktemp -d) 42 | 43 | # Set up a wheelhouse 44 | export WHEELHOUSE=${WHEELHOUSE:-$tmpdir/.wheelhouse} 45 | mkvenv $tmpdir/wheelhouse 46 | # Specific PIP version - must succeed to be useful. 47 | # - build/download a local wheel so we don't hit the network on each venv. 48 | if [ -n "${PBR_PIP_VERSION:-}" ]; then 49 | td=$(mktemp -d) 50 | $tmpdir/wheelhouse/bin/pip wheel -w $td $PBR_PIP_VERSION 51 | # This version will now be installed in every new venv. 52 | export PIPVERSION="$td/$(ls $td)" 53 | $tmpdir/wheelhouse/bin/pip install -U $PIPVERSION 54 | # We have pip in global-requirements as open-ended requirements, 55 | # but since we don't use -U in any other invocations, our version 56 | # of pip should be sticky. 57 | fi 58 | # Build wheels for everything so we don't hit the network on each venv. 59 | # Not all packages properly build wheels (httpretty for example). 60 | # Do our best but ignore errors when making wheels. 61 | set +e 62 | $tmpdir/wheelhouse/bin/pip $PIPFLAGS wheel -w $WHEELHOUSE -f $WHEELHOUSE -r \ 63 | $REPODIR/requirements/global-requirements.txt 64 | set -e 65 | 66 | #BRANCH 67 | BRANCH=${OVERRIDE_ZUUL_BRANCH=:-master} 68 | # PROJECTS is a list of projects that we're testing 69 | PROJECTS=$* 70 | 71 | pbrsdistdir=$tmpdir/pbrsdist 72 | git clone $REPODIR/pbr $pbrsdistdir 73 | cd $pbrsdistdir 74 | 75 | # Capture Zuul repo state info. Local master should be the current change. 76 | # origin/master should refer to the parent of the current change. If they 77 | # are the same then there is no change either from zuul or locally. 78 | git --git-dir $REPODIR/pbr/.git show --format=oneline --no-patch master 79 | git --git-dir $REPODIR/pbr/.git show --format=oneline --no-patch origin/master 80 | # If there is no diff between the branches then there is no local change. 81 | if ! git --git-dir $REPODIR/pbr/.git diff --quiet master..origin/master ; then 82 | git show --format=oneline --no-patch HEAD 83 | mkvenv wheel 84 | # TODO(clarkb) switch to using build tool here 85 | wheel/bin/pip install setuptools 86 | wheel/bin/python setup.py bdist_wheel 87 | PBR_CHANGE=1 88 | fi 89 | 90 | ##### Test Project Installation ##### 91 | # Create a test project and install it multiple different ways 92 | # using different tools to sanity check behavior is consistent 93 | # with what expect and doesn't change. 94 | # TODO(clarkb) Add test coverage for build and wheel tools too. 95 | eptest=$tmpdir/eptest 96 | mkdir $eptest 97 | cd $eptest 98 | 99 | cat < setup.cfg 100 | [metadata] 101 | name = test_project 102 | 103 | [entry_points] 104 | console_scripts = 105 | test_cmd = test_project:main 106 | EOF 107 | 108 | cat < setup.py 109 | import setuptools 110 | 111 | setuptools.setup( 112 | setup_requires=['pbr'], 113 | pbr=True) 114 | EOF 115 | 116 | mkdir test_project 117 | cat < test_project/__init__.py 118 | def main(): 119 | print("Test cmd") 120 | EOF 121 | 122 | eppbrdir=$tmpdir/eppbrdir 123 | git clone $REPODIR/pbr $eppbrdir 124 | 125 | function check_setuppy { 126 | local checkname 127 | checkname=$1 128 | 129 | local epvenv 130 | epvenv=$eptest/setuppyvenv_$checkname 131 | mkvenv $epvenv 132 | $epvenv/bin/pip $PIPFLAGS install -f $WHEELHOUSE -e $eppbrdir 133 | # We install setuptools only in this venv to check setup.py 134 | # behaviors. 135 | $epvenv/bin/pip $PIPFLAGS install -f $WHEELHOUSE setuptools 136 | 137 | # First check develop 138 | PBR_VERSION=0.0 $epvenv/bin/python setup.py develop 139 | cat $epvenv/bin/test_cmd 140 | grep 'PBR Generated' $epvenv/bin/test_cmd 141 | $epvenv/bin/test_cmd | grep 'Test cmd' 142 | PBR_VERSION=0.0 $epvenv/bin/python setup.py develop --uninstall 143 | 144 | # Now check install 145 | PBR_VERSION=0.0 $epvenv/bin/python setup.py install 146 | cat $epvenv/bin/test_cmd 147 | grep 'PBR Generated' $epvenv/bin/test_cmd 148 | $epvenv/bin/test_cmd | grep 'Test cmd' 149 | } 150 | 151 | function check_pip { 152 | local checkname 153 | checkname=$1 154 | 155 | local epvenv 156 | epvenv=$eptest/pipvenv_$checkname 157 | mkvenv $epvenv 158 | $epvenv/bin/pip $PIPFLAGS install -f $WHEELHOUSE -e $eppbrdir 159 | 160 | # First check develop 161 | PBR_VERSION=0.0 $epvenv/bin/pip install -e ./ 162 | cat $epvenv/bin/test_cmd 163 | if [ -f ./pyproject.toml ] ; then 164 | # Pip dev installs with pyproject.toml build from editable wheels 165 | # which do not use PBR generated console scripts. 166 | grep 'from test_project import main' $epvenv/bin/test_cmd 167 | ! grep 'PBR Generated' $epvenv/bin/test_cmd 168 | else 169 | # Otherwise we should get the PBR generated script 170 | grep 'PBR Generated' $epvenv/bin/test_cmd 171 | fi 172 | $epvenv/bin/test_cmd | grep 'Test cmd' 173 | PBR_VERSION=0.0 $epvenv/bin/pip uninstall -y test-project 174 | 175 | # Now check install 176 | PBR_VERSION=0.0 $epvenv/bin/pip install ./ 177 | cat $epvenv/bin/test_cmd 178 | # Pip installs install from wheel builds which do not use 179 | # PBR generated console scripts. 180 | grep 'from test_project import main' $epvenv/bin/test_cmd 181 | ! grep 'PBR Generated' $epvenv/bin/test_cmd 182 | $epvenv/bin/test_cmd | grep 'Test cmd' 183 | } 184 | 185 | ### No pyproject.toml ### 186 | # Check setup.py behavior 187 | check_setuppy nopyprojecttoml 188 | 189 | # Check pip behavior 190 | check_pip nopyprojecttoml 191 | 192 | ### pyproject.toml ### 193 | # Now write a pyproject.toml and recheck the results. 194 | # Note the pip install -e behavior differs. 195 | cat < pyproject.toml 196 | [build-system] 197 | requires = [ 198 | "pbr>=6.0.0", 199 | "setuptools>=64.0.0", 200 | ] 201 | build-backend = "pbr.build" 202 | EOF 203 | 204 | # Check setup.py behavior 205 | check_setuppy pyprojecttoml 206 | 207 | # Check pip behavior 208 | check_pip pyprojecttoml 209 | 210 | projectdir=$tmpdir/projects 211 | mkdir -p $projectdir 212 | sudo chown -R $USER $REPODIR 213 | 214 | export PBR_INTEGRATION=1 215 | export PIPFLAGS 216 | export PIPVERSION 217 | PBRVERSION=pbr 218 | if [ -n "$PBR_CHANGE" ] ; then 219 | PBRVERSION=$(ls $pbrsdistdir/dist/pbr-*.whl) 220 | fi 221 | export PBRVERSION 222 | export PROJECTS 223 | export REPODIR 224 | export WHEELHOUSE 225 | export OS_TEST_TIMEOUT=1200 226 | cd $REPODIR/pbr 227 | mkvenv .venv 228 | source .venv/bin/activate 229 | pip install -r test-requirements.txt 230 | pip install ${REPODIR}/requirements 231 | stestr run --suppress-attachments test_integration 232 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | minversion = 3.18.0 3 | envlist = pep8,py3,docs 4 | 5 | [testenv] 6 | usedevelop = true 7 | passenv = 8 | PBR_INTEGRATION 9 | PIPFLAGS 10 | PIPVERSION 11 | PBRVERSION 12 | REPODIR 13 | WHEELHOUSE 14 | PROJECTS 15 | setenv = 16 | OS_STDOUT_CAPTURE={env:OS_STDOUT_CAPTURE:1} 17 | OS_STDERR_CAPTURE={env:OS_STDERR_CAPTURE:1} 18 | OS_TEST_TIMEOUT={env:OS_TEST_TIMEOUT:60} 19 | # NOTE(stephenfin): pbr intentionally does not use constraints since we support 20 | # a broader range of Python versions than OpenStack as a whole 21 | deps = 22 | -r{toxinidir}/test-requirements.txt 23 | commands = stestr run --serial --suppress-attachments {posargs} 24 | 25 | # The latest pip that supports python3.6 assumes that pep660 editable 26 | # wheel installations should be used for development installs when the 27 | # project has a pyproject.toml file. Unfortunately, the latest setuptools 28 | # that supports python3.6 does not support pep660. This means the combo 29 | # breaks under python3.6. Workaround the problem by disabling development 30 | # installs for this version of python. 31 | [testenv:py36] 32 | usedevelop = false 33 | 34 | [testenv:pep8] 35 | commands = pre-commit run -a 36 | 37 | [testenv:docs] 38 | allowlist_externals = 39 | rm 40 | deps = 41 | -r{toxinidir}/doc/requirements.txt 42 | commands = 43 | rm -rf doc/build doc/source/reference/api 44 | python setup.py sdist 45 | sphinx-build -W -b html doc/source doc/build/html {posargs} 46 | 47 | [testenv:releasenotes] 48 | allowlist_externals = 49 | rm 50 | deps = {[testenv:docs]deps} 51 | commands = 52 | rm -rf releasenotes/build 53 | sphinx-build -W -b html -d releasenotes/build/doctrees releasenotes/source releasenotes/build/html 54 | 55 | [testenv:venv] 56 | commands = {posargs} 57 | 58 | [testenv:cover] 59 | setenv = 60 | PYTHON=coverage run --source pbr --parallel-mode 61 | commands = 62 | stestr run --serial --suppress-attachments {posargs} 63 | coverage combine 64 | coverage html -d cover 65 | coverage xml -o cover/coverage.xml 66 | 67 | [flake8] 68 | # W504 (you have to choose this or W503) 69 | # H216 we use mock instead of unittest.mock because we still test 70 | # against python2.7. 71 | ignore = W504,H216 72 | exclude = .venv,.tox,dist,doc,*.egg,build 73 | show-source = true 74 | --------------------------------------------------------------------------------