././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1609771032.8072078 check-manifest-0.46/0000775000175000017500000000000000000000000013357 5ustar00mgmg00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1588439465.0 check-manifest-0.46/.coveragerc0000644000175000017500000000030200000000000015471 0ustar00mgmg00000000000000[run] source = check_manifest [report] exclude_lines = pragma: nocover except ImportError: if __name__ == '__main__': if sys.platform == 'darwin': raise NotImplementedError ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1588509591.0 check-manifest-0.46/.gitignore0000664000175000017500000000016000000000000015344 0ustar00mgmg00000000000000.idea/ .vscode/ *.pyc __pycache__/ .tox/ *.egg-info/ dist/ tmp/ .coverage build/ tags coverage.xml .mypy_cache/ ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608651454.0 check-manifest-0.46/.pre-commit-hooks.yaml0000664000175000017500000000037000000000000017516 0ustar00mgmg00000000000000- id: check-manifest name: check-manifest description: Check the completeness of MANIFEST.in for Python packages. entry: check-manifest language: python language_version: python3 pass_filenames: false always_run: true ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609770891.0 check-manifest-0.46/CHANGES.rst0000664000175000017500000003166000000000000015167 0ustar00mgmg00000000000000Changelog ========= 0.46 (2021-01-04) ----------------- - The `pre-commit `__ hook now always uses Python 3. 0.45 (2020-10-31) ----------------- - Add Python 3.9 support. - Drop Python 3.5 support. - Switch from ``pep517`` to `python-build `__ ( `#128 `__). - Add ``--no-build-isolation`` option so check-manifest can succeed building pep517-based distributions without an internet connection. With ``--no-build-isolation``, you must preinstall the ``build-system.requires`` beforehand. ( `#128 `__). 0.44 (2020-10-03) ----------------- - Try to avoid passing ``--recurse-submodules`` to ``git ls`` if the project doesn't use git submodules (i.e. doesn't have a ``.gitsubmodules`` file). This should make check-manifest work again with older git versions, as long as you don't use submodules (`#124 `__). 0.43 (2020-09-21) ----------------- - Fix collecting files versioned by ``git`` when a project has submodules and ``GIT_INDEX_FILE`` is set. This bug was triggered when ``check-manifest`` was run as part of a git hook ( `#122 `__, `#123 `__). Note: check-manifest 0.43 requires ``git`` version 2.11 or later. 0.42 (2020-05-03) ----------------- - Added ``-q``/``--quiet`` command line argument. This will reduce the verbosity of informational output, e.g. for use in a CI pipeline. - Rewrote the ignore logic to be more compatible with setuptools. This might have introduced some regressions, so please file bugs! One side effect of this is that ``--ignore`` (or the ``ignore`` setting in the config file) is now handled the same way as ``global-exclude`` in a ``MANIFEST.in``, which means: - it's matched anywhere in the file tree - it's ignored if it matches a directory You can ignore directories only by ignoring every file inside it. You can use ``--ignore=dir/**`` to do that. This decision is not cast in stone: I may in the future change the handling of ``--ignore`` to match files and directories, because there's no reason it has to be setuptools-compatible. - Drop Python 2.7 support. 0.41 (2020-02-25) ----------------- - Support `PEP 517`_, i.e. packages using pyproject.toml instead of a setup.py (`#105 `_). .. _PEP 517: https://www.python.org/dev/peps/pep-0517/ - Ignore subcommand stderr unless the subcommand fails. This avoids treating warning messages as filenames. (`#110 `_.) 0.40 (2019-10-15) ----------------- - Add Python 3.8 support. 0.39 (2019-06-06) ----------------- - You can now use check-manifest as a `pre-commit `_ hook (`#100 `__). 0.38 (2019-04-23) ----------------- - Add Python 3.7 support. - Drop Python 3.4 support. - Added GitHub templates to default ignore patterns. - Added reading check-manifest config out of ``tox.ini`` or ``pyproject.toml``. 0.37 (2018-04-12) ----------------- - Drop Python 3.3 support. - Support packages using ``setuptools_scm`` (`#68 `__). Note that ``setuptools_scm`` usually makes MANIFEST.in files obsolete. Having one is helpful only if you intend to build an sdist and then use that sdist to perform further builds, instead of building from a source checkout. 0.36 (2017-11-21) ----------------- - Handle empty VCS repositories more gracefully (`#84 `__). 0.35 (2017-01-30) ----------------- - Python 3.6 support. 0.34 (2016-09-14) ----------------- - Fix WindowsError due to presence of read-only files (`#74 `__). 0.33 (2016-08-29) ----------------- - Fix WindowsError due to git submodules in subdirectories (`#73 `__). Contributed by Loren Gordon. 0.32 (2016-08-16) ----------------- * New config/command line option to ignore bad ideas (ignore-bad-ideas) (`issue #67 `__). Contributed by Brecht Machiels. * Files named ``.hgsigs`` are ignored by default. Contributed by Jakub Wilk. 0.31 (2016-01-28) ----------------- - Drop Python 3.2 support. - Ignore commented-out lines in MANIFEST.in (`issue #66 `__). 0.30 (2015-12-10) ----------------- * Support git submodules (`issue #61 `__). * Revert the zc.buildout support hack from 0.26 because it causes breakage (`issue #56 `__). * Improve non-ASCII filename handling with Bazaar on Windows. 0.29 (2015-11-21) ----------------- * Fix --python with just a command name, to be found in path (`issue #57 `__). 0.28 (2015-11-11) ----------------- * Fix detection of git repositories when .git is a file and not a directory (`#53 `__). One situation where this occurs is when the project is checked out as a git submodule. * Apply ignore patterns in subdirectories too (`#54 `__). 0.27 (2015-11-02) ----------------- * Fix utter breakage on Windows, introduced in 0.26 (`issue #52 `__). (The bug -- clearing the environment unnecessarily -- could probably also cause locale-related problems on other OSes.) 0.26 (2015-10-30) ----------------- * Do not complain about missing ``.gitattributes`` file (`PR #50 `__). * Normalize unicode representation and case of filenames. (`issue #47 `__). * Support installation via zc.buildout better (`issue #35 `__). * Drop Python 2.6 support because one of our test dependencies (mock) dropped it. This also means we no longer use environment markers. 0.25 (2015-05-27) ----------------- * Stop dynamic computation of install_requires in setup.py: this doesn't work well in the presence of the pip 7 wheel cache. Use PEP-426 environment markers instead (this means we now require setuptools >= 0.7, and pip >= 6.0, and wheel >= 0.24). 0.24 (2015-03-26) ----------------- * Make sure ``setup.py`` not being added to the VCS doesn't cause hard-to-understand errors (`issue #46 `__). 0.23 (2015-02-12) ----------------- * More reliable svn status parsing; now handles svn externals (`issue #45 `__). * The test suite now skips tests for version control systems that aren't installed (`issue #42 `__). 0.22 (2014-12-23) ----------------- * More terse output by default; use the new ``-v`` (``--verbose``) flag to see all the details. * Warn the user if MANIFEST.in is missing (`issue #31 `__). * Fix IOError when files listed under version control are missing (`issue #32 `__). * Improved wording of the match/do not match messages (`issue #34 `__). * Handle a relative --python path (`issue #36 `__). * Warn about leading and trailing slashes in MANIFEST.in (`issue #37 `__). * Ignore .travis.yml by default (`issue #39 `__). * Suggest a rule for Makefile found deeper in the source tree. 0.21 (2014-06-13) ----------------- * Don't drop setup.cfg when copying version-controlled files into a clean temporary directory (`issue #29 `__). 0.20 (2014-05-14) ----------------- * Restore warning about files included in the sdist but not added to the version control system (`issue #27 `__). * Fix ``check-manifest relative/pathname`` (`issue #28 `__). 0.19 (2014-02-09) ----------------- * More correct MANIFEST.in parsing for exclusion rules. * Some effort was expended towards Windows compatibility. * Handles non-ASCII filenames, as long as they're valid in your locale (`issue #23 `__, `#25 `__). 0.18 (2014-01-30) ----------------- * Friendlier error message when an external command cannot be found (`issue #21 `__). * Add suggestion pattern for `.coveragerc`. * Python 2.6 support (`issue #22 `__). 0.17 (2013-10-10) ----------------- * Read the existing MANIFEST.in file for files to ignore (`issue #19 `__). 0.16 (2013-10-01) ----------------- * Fix Subversion status parsing in the presence of svn usernames longer than 12 characters (`issue #18 `__). 0.15 (2013-09-20) ----------------- * Normalize the paths of all files, avoiding some duplicate misses of directories. (`issue #16 `__). [maurits] 0.14 (2013-08-28) ----------------- * Supports packages that do not live in the root of a version control repository (`issue #15 `__). * More reliable svn support: detect files that have been added but not committed (or committed but not updated). * Licence changed from GPL (v2 or later) to MIT (`issue #12 `__). 0.13 (2013-07-31) ----------------- * New command line option: --ignore (`issue #11 `__). Contributed by Steven Myint. * New command line option: -p, --python. Defaults to the Python you used to run check-manifest. Fixes issues with packages that require Python 3 to run setup.py (`issue #13 `__). 0.12 (2013-05-15) ----------------- * Add suggestion pattern for `Makefile`. * More generic suggestion patterns, should cover almost anything. * zest.releaser_ integration: skip check-release for non-Python packages (`issue #9 `__). 0.11 (2013-03-20) ----------------- * Make sure ``MANIFEST.in`` is not ignored even if it hasn't been added to the VCS yet (`issue #7 `__). 0.10 (2013-03-17) ----------------- * ``check-manifest --version`` now prints the version number. * Don't apologize for not adding rules for directories (especially after adding rules that include files inside that directory). * Python 3 support contributed by Steven Myint. * Default ignore patterns can be configured in ``setup.cfg`` (`issue #3 `_). 0.9 (2013-03-06) ---------------- * Add suggestion pattern for `.travis.yml`. * When check-manifest -u (or -c) doesn't know how to write a rule matching a particular file, it now apologizes explicitly. * Copy the source tree to a temporary directory before running python setup.py sdist to avoid side effects from setuptools plugins or stale \*.egg-info/SOURCES.txt files (`issue #1 `_). * Warn if `*.egg-info` or `*.mo` is actually checked into the VCS. * Don't complain if `*.mo` files are present in the sdist but not in the VCS (`issue #2 `_). 0.8 (2013-03-06) ---------------- * Entry point for zest.releaser_. If you install both zest.releaser and check-manifest, you will be asked if you want to check your manifest during ``fullrelease``. .. _zest.releaser: https://pypi.python.org/pypi/zest.releaser 0.7 (2013-03-05) ---------------- * First release available from the Python Package Index. * Moved from https://gist.github.com/4277075 to https://github.com/mgedmin/check-manifest * Added README.rst, CHANGES.rst, setup.py, tox.ini (but no real tests yet), MANIFEST.in, and a Makefile. * Fixed a bug in error reporting (when setup.py failed, the user would get `TypeError: descriptor '__init__' requires an 'exceptions.Exception' object but received a 'str'`). ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1560428570.0 check-manifest-0.46/LICENSE.rst0000644000175000017500000000211300000000000015166 0ustar00mgmg00000000000000The MIT License (MIT) Copyright (c) 2013 Marius Gedminas and contributors Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608651454.0 check-manifest-0.46/MANIFEST.in0000664000175000017500000000026200000000000015115 0ustar00mgmg00000000000000include *.rst include *.py include *.yaml include tox.ini include Makefile include .gitignore include appveyor.yml include .coveragerc # added by check_manifest.py include *.mk ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1606719199.0 check-manifest-0.46/Makefile0000775000175000017500000000235500000000000015027 0ustar00mgmg00000000000000.PHONY: all all: @echo "Nothing to build. Try 'make test' perhaps?" .PHONY: test test: ##: run tests tox -p auto .PHONY: check check: ##: run tests without skipping any # 'make check' is defined in release.mk and here's how you can override it define check_recipe = SKIP_NO_TESTS=1 tox endef .PHONY: coverage coverage: ##: measure test coverage tox -e coverage .PHONY: diff-cover diff-cover: coverage ##: show untested code in this branch coverage xml diff-cover coverage.xml .PHONY: distcheck distcheck: distcheck-self # also release.mk will add other checks .PHONY: distcheck-self distcheck-self: tox -e check-manifest .PHONY: releasechecklist releasechecklist: check-readme # also release.mk will add other checks .PHONY: check-readme check-readme: @rev_line=' rev: "'"`$(PYTHON) setup.py --version`"'"' && \ grep -q "^$$rev_line$$" README.rst || { \ echo "README.rst doesn't specify $$rev_line"; \ echo "Please run make update-readme"; exit 1; } .PHONY: update-readme update-readme: sed -i -e 's/rev: ".*"/rev: "$(shell $(PYTHON) setup.py --version)"/' README.rst FILE_WITH_VERSION = check_manifest.py include release.mk ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1609771032.8072078 check-manifest-0.46/PKG-INFO0000664000175000017500000005747300000000000014474 0ustar00mgmg00000000000000Metadata-Version: 2.1 Name: check-manifest Version: 0.46 Summary: Check MANIFEST.in in a Python source package for completeness Home-page: https://github.com/mgedmin/check-manifest Author: Marius Gedminas Author-email: marius@gedmin.as License: MIT Description: check-manifest ============== |buildstatus|_ |appveyor|_ |coverage|_ Are you a Python developer? Have you uploaded packages to the Python Package Index? Have you accidentally uploaded *broken* packages with some files missing? If so, check-manifest is for you. Quick start ----------- :: $ pip install check-manifest $ cd ~/src/mygreatpackage $ check-manifest You can ask the script to help you update your MANIFEST.in:: $ check-manifest -u -v listing source files under version control: 6 files and directories building an sdist: check-manifest-0.7.tar.gz: 4 files and directories lists of files in version control and sdist do not match! missing from sdist: tests.py tox.ini suggested MANIFEST.in rules: include *.py include tox.ini updating MANIFEST.in $ cat MANIFEST.in include *.rst # added by check_manifest.py include *.py include tox.ini Command-line reference ---------------------- :: $ check-manifest --help usage: check-manifest [-h] [--version] [-v] [-c] [-u] [-p PYTHON] [--ignore patterns] [source_tree] Check a Python MANIFEST.in file for completeness positional arguments: source_tree location for the source tree (default: .) optional arguments: -h, --help show this help message and exit --version show program's version number and exit -v, --verbose more verbose output (default: False) -c, --create create a MANIFEST.in if missing (default: False) -u, --update append suggestions to MANIFEST.in (implies --create) (default: False) -p PYTHON, --python PYTHON use this Python interpreter for running setup.py sdist (default: /home/mg/.venv/bin/python) --ignore patterns ignore files/directories matching these comma- separated patterns (default: None) --ignore-bad-ideas patterns ignore bad idea files/directories matching these comma-separated patterns (default: []) Configuration ------------- You can configure check-manifest to ignore certain file patterns using a ``[tool.check-manifest]`` section in your ``pyproject.toml`` file or a ``[check-manifest]`` section in either ``setup.cfg`` or ``tox.ini``. Examples:: # pyproject.toml [tool.check-manifest] ignore = [".travis.yml"] # setup.cfg or tox.ini [check-manifest] ignore = .travis.yml Note that lists are newline separated in the ``setup.cfg`` and ``tox.ini`` files. The following options are recognized: ignore A list of filename patterns that will be ignored by check-manifest. Use this if you want to keep files in your version control system that shouldn't be included in your source distributions. The default ignore list is :: PKG-INFO *.egg-info *.egg-info/* setup.cfg .hgtags .hgsigs .hgignore .gitignore .bzrignore .gitattributes .github/* .travis.yml Jenkinsfile *.mo ignore-default-rules If set to ``true``, your ``ignore`` patterns will replace the default ignore list instead of adding to it. ignore-bad-ideas A list of filename patterns that will be ignored by check-manifest's generated files check. Use this if you want to keep generated files in your version control system, even though it is generally a bad idea. Version control integration --------------------------- With `pre-commit `_, check-manifest can be part of your git-workflow. Add the following to your ``.pre-commit-config.yaml``. .. code-block:: yaml repos: - repo: https://github.com/mgedmin/check-manifest rev: "0.46" hooks: - id: check-manifest If you are running pre-commit without a network, you can utilize ``args: [--no-build-isolation]`` to prevent a ``pip install`` reaching out to pypi. If you have additional ``build-system.requires`` outside of pip / setuptools / wheel you will want to list those in ``additional_dependencies``. .. code-block:: yaml repos: - repo: https://github.com/mgedmin/check-manifest rev: ... # pick a valid tag / revision hooks: - id: check-manifest args: [--no-build-isolation] additional_dependencies: [setuptools-scm] .. |buildstatus| image:: https://github.com/mgedmin/check-manifest/workflows/build/badge.svg?branch=master .. _buildstatus: https://github.com/mgedmin/check-manifest/actions .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/mgedmin/check-manifest?branch=master&svg=true .. _appveyor: https://ci.appveyor.com/project/mgedmin/check-manifest .. |coverage| image:: https://coveralls.io/repos/mgedmin/check-manifest/badge.svg?branch=master .. _coverage: https://coveralls.io/r/mgedmin/check-manifest Changelog ========= 0.46 (2021-01-04) ----------------- - The `pre-commit `__ hook now always uses Python 3. 0.45 (2020-10-31) ----------------- - Add Python 3.9 support. - Drop Python 3.5 support. - Switch from ``pep517`` to `python-build `__ ( `#128 `__). - Add ``--no-build-isolation`` option so check-manifest can succeed building pep517-based distributions without an internet connection. With ``--no-build-isolation``, you must preinstall the ``build-system.requires`` beforehand. ( `#128 `__). 0.44 (2020-10-03) ----------------- - Try to avoid passing ``--recurse-submodules`` to ``git ls`` if the project doesn't use git submodules (i.e. doesn't have a ``.gitsubmodules`` file). This should make check-manifest work again with older git versions, as long as you don't use submodules (`#124 `__). 0.43 (2020-09-21) ----------------- - Fix collecting files versioned by ``git`` when a project has submodules and ``GIT_INDEX_FILE`` is set. This bug was triggered when ``check-manifest`` was run as part of a git hook ( `#122 `__, `#123 `__). Note: check-manifest 0.43 requires ``git`` version 2.11 or later. 0.42 (2020-05-03) ----------------- - Added ``-q``/``--quiet`` command line argument. This will reduce the verbosity of informational output, e.g. for use in a CI pipeline. - Rewrote the ignore logic to be more compatible with setuptools. This might have introduced some regressions, so please file bugs! One side effect of this is that ``--ignore`` (or the ``ignore`` setting in the config file) is now handled the same way as ``global-exclude`` in a ``MANIFEST.in``, which means: - it's matched anywhere in the file tree - it's ignored if it matches a directory You can ignore directories only by ignoring every file inside it. You can use ``--ignore=dir/**`` to do that. This decision is not cast in stone: I may in the future change the handling of ``--ignore`` to match files and directories, because there's no reason it has to be setuptools-compatible. - Drop Python 2.7 support. 0.41 (2020-02-25) ----------------- - Support `PEP 517`_, i.e. packages using pyproject.toml instead of a setup.py (`#105 `_). .. _PEP 517: https://www.python.org/dev/peps/pep-0517/ - Ignore subcommand stderr unless the subcommand fails. This avoids treating warning messages as filenames. (`#110 `_.) 0.40 (2019-10-15) ----------------- - Add Python 3.8 support. 0.39 (2019-06-06) ----------------- - You can now use check-manifest as a `pre-commit `_ hook (`#100 `__). 0.38 (2019-04-23) ----------------- - Add Python 3.7 support. - Drop Python 3.4 support. - Added GitHub templates to default ignore patterns. - Added reading check-manifest config out of ``tox.ini`` or ``pyproject.toml``. 0.37 (2018-04-12) ----------------- - Drop Python 3.3 support. - Support packages using ``setuptools_scm`` (`#68 `__). Note that ``setuptools_scm`` usually makes MANIFEST.in files obsolete. Having one is helpful only if you intend to build an sdist and then use that sdist to perform further builds, instead of building from a source checkout. 0.36 (2017-11-21) ----------------- - Handle empty VCS repositories more gracefully (`#84 `__). 0.35 (2017-01-30) ----------------- - Python 3.6 support. 0.34 (2016-09-14) ----------------- - Fix WindowsError due to presence of read-only files (`#74 `__). 0.33 (2016-08-29) ----------------- - Fix WindowsError due to git submodules in subdirectories (`#73 `__). Contributed by Loren Gordon. 0.32 (2016-08-16) ----------------- * New config/command line option to ignore bad ideas (ignore-bad-ideas) (`issue #67 `__). Contributed by Brecht Machiels. * Files named ``.hgsigs`` are ignored by default. Contributed by Jakub Wilk. 0.31 (2016-01-28) ----------------- - Drop Python 3.2 support. - Ignore commented-out lines in MANIFEST.in (`issue #66 `__). 0.30 (2015-12-10) ----------------- * Support git submodules (`issue #61 `__). * Revert the zc.buildout support hack from 0.26 because it causes breakage (`issue #56 `__). * Improve non-ASCII filename handling with Bazaar on Windows. 0.29 (2015-11-21) ----------------- * Fix --python with just a command name, to be found in path (`issue #57 `__). 0.28 (2015-11-11) ----------------- * Fix detection of git repositories when .git is a file and not a directory (`#53 `__). One situation where this occurs is when the project is checked out as a git submodule. * Apply ignore patterns in subdirectories too (`#54 `__). 0.27 (2015-11-02) ----------------- * Fix utter breakage on Windows, introduced in 0.26 (`issue #52 `__). (The bug -- clearing the environment unnecessarily -- could probably also cause locale-related problems on other OSes.) 0.26 (2015-10-30) ----------------- * Do not complain about missing ``.gitattributes`` file (`PR #50 `__). * Normalize unicode representation and case of filenames. (`issue #47 `__). * Support installation via zc.buildout better (`issue #35 `__). * Drop Python 2.6 support because one of our test dependencies (mock) dropped it. This also means we no longer use environment markers. 0.25 (2015-05-27) ----------------- * Stop dynamic computation of install_requires in setup.py: this doesn't work well in the presence of the pip 7 wheel cache. Use PEP-426 environment markers instead (this means we now require setuptools >= 0.7, and pip >= 6.0, and wheel >= 0.24). 0.24 (2015-03-26) ----------------- * Make sure ``setup.py`` not being added to the VCS doesn't cause hard-to-understand errors (`issue #46 `__). 0.23 (2015-02-12) ----------------- * More reliable svn status parsing; now handles svn externals (`issue #45 `__). * The test suite now skips tests for version control systems that aren't installed (`issue #42 `__). 0.22 (2014-12-23) ----------------- * More terse output by default; use the new ``-v`` (``--verbose``) flag to see all the details. * Warn the user if MANIFEST.in is missing (`issue #31 `__). * Fix IOError when files listed under version control are missing (`issue #32 `__). * Improved wording of the match/do not match messages (`issue #34 `__). * Handle a relative --python path (`issue #36 `__). * Warn about leading and trailing slashes in MANIFEST.in (`issue #37 `__). * Ignore .travis.yml by default (`issue #39 `__). * Suggest a rule for Makefile found deeper in the source tree. 0.21 (2014-06-13) ----------------- * Don't drop setup.cfg when copying version-controlled files into a clean temporary directory (`issue #29 `__). 0.20 (2014-05-14) ----------------- * Restore warning about files included in the sdist but not added to the version control system (`issue #27 `__). * Fix ``check-manifest relative/pathname`` (`issue #28 `__). 0.19 (2014-02-09) ----------------- * More correct MANIFEST.in parsing for exclusion rules. * Some effort was expended towards Windows compatibility. * Handles non-ASCII filenames, as long as they're valid in your locale (`issue #23 `__, `#25 `__). 0.18 (2014-01-30) ----------------- * Friendlier error message when an external command cannot be found (`issue #21 `__). * Add suggestion pattern for `.coveragerc`. * Python 2.6 support (`issue #22 `__). 0.17 (2013-10-10) ----------------- * Read the existing MANIFEST.in file for files to ignore (`issue #19 `__). 0.16 (2013-10-01) ----------------- * Fix Subversion status parsing in the presence of svn usernames longer than 12 characters (`issue #18 `__). 0.15 (2013-09-20) ----------------- * Normalize the paths of all files, avoiding some duplicate misses of directories. (`issue #16 `__). [maurits] 0.14 (2013-08-28) ----------------- * Supports packages that do not live in the root of a version control repository (`issue #15 `__). * More reliable svn support: detect files that have been added but not committed (or committed but not updated). * Licence changed from GPL (v2 or later) to MIT (`issue #12 `__). 0.13 (2013-07-31) ----------------- * New command line option: --ignore (`issue #11 `__). Contributed by Steven Myint. * New command line option: -p, --python. Defaults to the Python you used to run check-manifest. Fixes issues with packages that require Python 3 to run setup.py (`issue #13 `__). 0.12 (2013-05-15) ----------------- * Add suggestion pattern for `Makefile`. * More generic suggestion patterns, should cover almost anything. * zest.releaser_ integration: skip check-release for non-Python packages (`issue #9 `__). 0.11 (2013-03-20) ----------------- * Make sure ``MANIFEST.in`` is not ignored even if it hasn't been added to the VCS yet (`issue #7 `__). 0.10 (2013-03-17) ----------------- * ``check-manifest --version`` now prints the version number. * Don't apologize for not adding rules for directories (especially after adding rules that include files inside that directory). * Python 3 support contributed by Steven Myint. * Default ignore patterns can be configured in ``setup.cfg`` (`issue #3 `_). 0.9 (2013-03-06) ---------------- * Add suggestion pattern for `.travis.yml`. * When check-manifest -u (or -c) doesn't know how to write a rule matching a particular file, it now apologizes explicitly. * Copy the source tree to a temporary directory before running python setup.py sdist to avoid side effects from setuptools plugins or stale \*.egg-info/SOURCES.txt files (`issue #1 `_). * Warn if `*.egg-info` or `*.mo` is actually checked into the VCS. * Don't complain if `*.mo` files are present in the sdist but not in the VCS (`issue #2 `_). 0.8 (2013-03-06) ---------------- * Entry point for zest.releaser_. If you install both zest.releaser and check-manifest, you will be asked if you want to check your manifest during ``fullrelease``. .. _zest.releaser: https://pypi.python.org/pypi/zest.releaser 0.7 (2013-03-05) ---------------- * First release available from the Python Package Index. * Moved from https://gist.github.com/4277075 to https://github.com/mgedmin/check-manifest * Added README.rst, CHANGES.rst, setup.py, tox.ini (but no real tests yet), MANIFEST.in, and a Makefile. * Fixed a bug in error reporting (when setup.py failed, the user would get `TypeError: descriptor '__init__' requires an 'exceptions.Exception' object but received a 'str'`). Keywords: distutils,setuptools,packaging,manifest,checker,linter Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: test ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609770899.0 check-manifest-0.46/README.rst0000664000175000017500000001201200000000000015042 0ustar00mgmg00000000000000check-manifest ============== |buildstatus|_ |appveyor|_ |coverage|_ Are you a Python developer? Have you uploaded packages to the Python Package Index? Have you accidentally uploaded *broken* packages with some files missing? If so, check-manifest is for you. Quick start ----------- :: $ pip install check-manifest $ cd ~/src/mygreatpackage $ check-manifest You can ask the script to help you update your MANIFEST.in:: $ check-manifest -u -v listing source files under version control: 6 files and directories building an sdist: check-manifest-0.7.tar.gz: 4 files and directories lists of files in version control and sdist do not match! missing from sdist: tests.py tox.ini suggested MANIFEST.in rules: include *.py include tox.ini updating MANIFEST.in $ cat MANIFEST.in include *.rst # added by check_manifest.py include *.py include tox.ini Command-line reference ---------------------- :: $ check-manifest --help usage: check-manifest [-h] [--version] [-v] [-c] [-u] [-p PYTHON] [--ignore patterns] [source_tree] Check a Python MANIFEST.in file for completeness positional arguments: source_tree location for the source tree (default: .) optional arguments: -h, --help show this help message and exit --version show program's version number and exit -v, --verbose more verbose output (default: False) -c, --create create a MANIFEST.in if missing (default: False) -u, --update append suggestions to MANIFEST.in (implies --create) (default: False) -p PYTHON, --python PYTHON use this Python interpreter for running setup.py sdist (default: /home/mg/.venv/bin/python) --ignore patterns ignore files/directories matching these comma- separated patterns (default: None) --ignore-bad-ideas patterns ignore bad idea files/directories matching these comma-separated patterns (default: []) Configuration ------------- You can configure check-manifest to ignore certain file patterns using a ``[tool.check-manifest]`` section in your ``pyproject.toml`` file or a ``[check-manifest]`` section in either ``setup.cfg`` or ``tox.ini``. Examples:: # pyproject.toml [tool.check-manifest] ignore = [".travis.yml"] # setup.cfg or tox.ini [check-manifest] ignore = .travis.yml Note that lists are newline separated in the ``setup.cfg`` and ``tox.ini`` files. The following options are recognized: ignore A list of filename patterns that will be ignored by check-manifest. Use this if you want to keep files in your version control system that shouldn't be included in your source distributions. The default ignore list is :: PKG-INFO *.egg-info *.egg-info/* setup.cfg .hgtags .hgsigs .hgignore .gitignore .bzrignore .gitattributes .github/* .travis.yml Jenkinsfile *.mo ignore-default-rules If set to ``true``, your ``ignore`` patterns will replace the default ignore list instead of adding to it. ignore-bad-ideas A list of filename patterns that will be ignored by check-manifest's generated files check. Use this if you want to keep generated files in your version control system, even though it is generally a bad idea. Version control integration --------------------------- With `pre-commit `_, check-manifest can be part of your git-workflow. Add the following to your ``.pre-commit-config.yaml``. .. code-block:: yaml repos: - repo: https://github.com/mgedmin/check-manifest rev: "0.46" hooks: - id: check-manifest If you are running pre-commit without a network, you can utilize ``args: [--no-build-isolation]`` to prevent a ``pip install`` reaching out to pypi. If you have additional ``build-system.requires`` outside of pip / setuptools / wheel you will want to list those in ``additional_dependencies``. .. code-block:: yaml repos: - repo: https://github.com/mgedmin/check-manifest rev: ... # pick a valid tag / revision hooks: - id: check-manifest args: [--no-build-isolation] additional_dependencies: [setuptools-scm] .. |buildstatus| image:: https://github.com/mgedmin/check-manifest/workflows/build/badge.svg?branch=master .. _buildstatus: https://github.com/mgedmin/check-manifest/actions .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/mgedmin/check-manifest?branch=master&svg=true .. _appveyor: https://ci.appveyor.com/project/mgedmin/check-manifest .. |coverage| image:: https://coveralls.io/repos/mgedmin/check-manifest/badge.svg?branch=master .. _coverage: https://coveralls.io/r/mgedmin/check-manifest ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1603999009.0 check-manifest-0.46/appveyor.yml0000664000175000017500000000167500000000000015760 0ustar00mgmg00000000000000version: build-{build}-{branch} environment: matrix: # https://www.appveyor.com/docs/installed-software#python lists available # versions - PYTHON: "C:\\Python36" - PYTHON: "C:\\Python37" - PYTHON: "C:\\Python38" - PYTHON: "C:\\Python39" init: - "echo %PYTHON%" install: - ps: | if (-not (Test-Path $env:PYTHON)) { curl -o install_python.ps1 https://raw.githubusercontent.com/matthew-brett/multibuild/11a389d78892cf90addac8f69433d5e22bfa422a/install_python.ps1 .\install_python.ps1 } - ps: if (-not (Test-Path $env:PYTHON)) { throw "No $env:PYTHON" } - "set PATH=%PYTHON%;%PYTHON%\\Scripts;%PATH%" - python --version - pip install -U virtualenv # upgrade pip in tox's virtualenvs - pip install tox - choco install bzr - "set PATH=C:\\Program Files (x86)\\Bazaar;%PATH%" - bzr --version - git --version - svn --version - hg --version build: off test_script: - tox -e py ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1609771032.8072078 check-manifest-0.46/check_manifest.egg-info/0000775000175000017500000000000000000000000020014 5ustar00mgmg00000000000000././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609771032.0 check-manifest-0.46/check_manifest.egg-info/PKG-INFO0000644000175000017500000005747300000000000021127 0ustar00mgmg00000000000000Metadata-Version: 2.1 Name: check-manifest Version: 0.46 Summary: Check MANIFEST.in in a Python source package for completeness Home-page: https://github.com/mgedmin/check-manifest Author: Marius Gedminas Author-email: marius@gedmin.as License: MIT Description: check-manifest ============== |buildstatus|_ |appveyor|_ |coverage|_ Are you a Python developer? Have you uploaded packages to the Python Package Index? Have you accidentally uploaded *broken* packages with some files missing? If so, check-manifest is for you. Quick start ----------- :: $ pip install check-manifest $ cd ~/src/mygreatpackage $ check-manifest You can ask the script to help you update your MANIFEST.in:: $ check-manifest -u -v listing source files under version control: 6 files and directories building an sdist: check-manifest-0.7.tar.gz: 4 files and directories lists of files in version control and sdist do not match! missing from sdist: tests.py tox.ini suggested MANIFEST.in rules: include *.py include tox.ini updating MANIFEST.in $ cat MANIFEST.in include *.rst # added by check_manifest.py include *.py include tox.ini Command-line reference ---------------------- :: $ check-manifest --help usage: check-manifest [-h] [--version] [-v] [-c] [-u] [-p PYTHON] [--ignore patterns] [source_tree] Check a Python MANIFEST.in file for completeness positional arguments: source_tree location for the source tree (default: .) optional arguments: -h, --help show this help message and exit --version show program's version number and exit -v, --verbose more verbose output (default: False) -c, --create create a MANIFEST.in if missing (default: False) -u, --update append suggestions to MANIFEST.in (implies --create) (default: False) -p PYTHON, --python PYTHON use this Python interpreter for running setup.py sdist (default: /home/mg/.venv/bin/python) --ignore patterns ignore files/directories matching these comma- separated patterns (default: None) --ignore-bad-ideas patterns ignore bad idea files/directories matching these comma-separated patterns (default: []) Configuration ------------- You can configure check-manifest to ignore certain file patterns using a ``[tool.check-manifest]`` section in your ``pyproject.toml`` file or a ``[check-manifest]`` section in either ``setup.cfg`` or ``tox.ini``. Examples:: # pyproject.toml [tool.check-manifest] ignore = [".travis.yml"] # setup.cfg or tox.ini [check-manifest] ignore = .travis.yml Note that lists are newline separated in the ``setup.cfg`` and ``tox.ini`` files. The following options are recognized: ignore A list of filename patterns that will be ignored by check-manifest. Use this if you want to keep files in your version control system that shouldn't be included in your source distributions. The default ignore list is :: PKG-INFO *.egg-info *.egg-info/* setup.cfg .hgtags .hgsigs .hgignore .gitignore .bzrignore .gitattributes .github/* .travis.yml Jenkinsfile *.mo ignore-default-rules If set to ``true``, your ``ignore`` patterns will replace the default ignore list instead of adding to it. ignore-bad-ideas A list of filename patterns that will be ignored by check-manifest's generated files check. Use this if you want to keep generated files in your version control system, even though it is generally a bad idea. Version control integration --------------------------- With `pre-commit `_, check-manifest can be part of your git-workflow. Add the following to your ``.pre-commit-config.yaml``. .. code-block:: yaml repos: - repo: https://github.com/mgedmin/check-manifest rev: "0.46" hooks: - id: check-manifest If you are running pre-commit without a network, you can utilize ``args: [--no-build-isolation]`` to prevent a ``pip install`` reaching out to pypi. If you have additional ``build-system.requires`` outside of pip / setuptools / wheel you will want to list those in ``additional_dependencies``. .. code-block:: yaml repos: - repo: https://github.com/mgedmin/check-manifest rev: ... # pick a valid tag / revision hooks: - id: check-manifest args: [--no-build-isolation] additional_dependencies: [setuptools-scm] .. |buildstatus| image:: https://github.com/mgedmin/check-manifest/workflows/build/badge.svg?branch=master .. _buildstatus: https://github.com/mgedmin/check-manifest/actions .. |appveyor| image:: https://ci.appveyor.com/api/projects/status/github/mgedmin/check-manifest?branch=master&svg=true .. _appveyor: https://ci.appveyor.com/project/mgedmin/check-manifest .. |coverage| image:: https://coveralls.io/repos/mgedmin/check-manifest/badge.svg?branch=master .. _coverage: https://coveralls.io/r/mgedmin/check-manifest Changelog ========= 0.46 (2021-01-04) ----------------- - The `pre-commit `__ hook now always uses Python 3. 0.45 (2020-10-31) ----------------- - Add Python 3.9 support. - Drop Python 3.5 support. - Switch from ``pep517`` to `python-build `__ ( `#128 `__). - Add ``--no-build-isolation`` option so check-manifest can succeed building pep517-based distributions without an internet connection. With ``--no-build-isolation``, you must preinstall the ``build-system.requires`` beforehand. ( `#128 `__). 0.44 (2020-10-03) ----------------- - Try to avoid passing ``--recurse-submodules`` to ``git ls`` if the project doesn't use git submodules (i.e. doesn't have a ``.gitsubmodules`` file). This should make check-manifest work again with older git versions, as long as you don't use submodules (`#124 `__). 0.43 (2020-09-21) ----------------- - Fix collecting files versioned by ``git`` when a project has submodules and ``GIT_INDEX_FILE`` is set. This bug was triggered when ``check-manifest`` was run as part of a git hook ( `#122 `__, `#123 `__). Note: check-manifest 0.43 requires ``git`` version 2.11 or later. 0.42 (2020-05-03) ----------------- - Added ``-q``/``--quiet`` command line argument. This will reduce the verbosity of informational output, e.g. for use in a CI pipeline. - Rewrote the ignore logic to be more compatible with setuptools. This might have introduced some regressions, so please file bugs! One side effect of this is that ``--ignore`` (or the ``ignore`` setting in the config file) is now handled the same way as ``global-exclude`` in a ``MANIFEST.in``, which means: - it's matched anywhere in the file tree - it's ignored if it matches a directory You can ignore directories only by ignoring every file inside it. You can use ``--ignore=dir/**`` to do that. This decision is not cast in stone: I may in the future change the handling of ``--ignore`` to match files and directories, because there's no reason it has to be setuptools-compatible. - Drop Python 2.7 support. 0.41 (2020-02-25) ----------------- - Support `PEP 517`_, i.e. packages using pyproject.toml instead of a setup.py (`#105 `_). .. _PEP 517: https://www.python.org/dev/peps/pep-0517/ - Ignore subcommand stderr unless the subcommand fails. This avoids treating warning messages as filenames. (`#110 `_.) 0.40 (2019-10-15) ----------------- - Add Python 3.8 support. 0.39 (2019-06-06) ----------------- - You can now use check-manifest as a `pre-commit `_ hook (`#100 `__). 0.38 (2019-04-23) ----------------- - Add Python 3.7 support. - Drop Python 3.4 support. - Added GitHub templates to default ignore patterns. - Added reading check-manifest config out of ``tox.ini`` or ``pyproject.toml``. 0.37 (2018-04-12) ----------------- - Drop Python 3.3 support. - Support packages using ``setuptools_scm`` (`#68 `__). Note that ``setuptools_scm`` usually makes MANIFEST.in files obsolete. Having one is helpful only if you intend to build an sdist and then use that sdist to perform further builds, instead of building from a source checkout. 0.36 (2017-11-21) ----------------- - Handle empty VCS repositories more gracefully (`#84 `__). 0.35 (2017-01-30) ----------------- - Python 3.6 support. 0.34 (2016-09-14) ----------------- - Fix WindowsError due to presence of read-only files (`#74 `__). 0.33 (2016-08-29) ----------------- - Fix WindowsError due to git submodules in subdirectories (`#73 `__). Contributed by Loren Gordon. 0.32 (2016-08-16) ----------------- * New config/command line option to ignore bad ideas (ignore-bad-ideas) (`issue #67 `__). Contributed by Brecht Machiels. * Files named ``.hgsigs`` are ignored by default. Contributed by Jakub Wilk. 0.31 (2016-01-28) ----------------- - Drop Python 3.2 support. - Ignore commented-out lines in MANIFEST.in (`issue #66 `__). 0.30 (2015-12-10) ----------------- * Support git submodules (`issue #61 `__). * Revert the zc.buildout support hack from 0.26 because it causes breakage (`issue #56 `__). * Improve non-ASCII filename handling with Bazaar on Windows. 0.29 (2015-11-21) ----------------- * Fix --python with just a command name, to be found in path (`issue #57 `__). 0.28 (2015-11-11) ----------------- * Fix detection of git repositories when .git is a file and not a directory (`#53 `__). One situation where this occurs is when the project is checked out as a git submodule. * Apply ignore patterns in subdirectories too (`#54 `__). 0.27 (2015-11-02) ----------------- * Fix utter breakage on Windows, introduced in 0.26 (`issue #52 `__). (The bug -- clearing the environment unnecessarily -- could probably also cause locale-related problems on other OSes.) 0.26 (2015-10-30) ----------------- * Do not complain about missing ``.gitattributes`` file (`PR #50 `__). * Normalize unicode representation and case of filenames. (`issue #47 `__). * Support installation via zc.buildout better (`issue #35 `__). * Drop Python 2.6 support because one of our test dependencies (mock) dropped it. This also means we no longer use environment markers. 0.25 (2015-05-27) ----------------- * Stop dynamic computation of install_requires in setup.py: this doesn't work well in the presence of the pip 7 wheel cache. Use PEP-426 environment markers instead (this means we now require setuptools >= 0.7, and pip >= 6.0, and wheel >= 0.24). 0.24 (2015-03-26) ----------------- * Make sure ``setup.py`` not being added to the VCS doesn't cause hard-to-understand errors (`issue #46 `__). 0.23 (2015-02-12) ----------------- * More reliable svn status parsing; now handles svn externals (`issue #45 `__). * The test suite now skips tests for version control systems that aren't installed (`issue #42 `__). 0.22 (2014-12-23) ----------------- * More terse output by default; use the new ``-v`` (``--verbose``) flag to see all the details. * Warn the user if MANIFEST.in is missing (`issue #31 `__). * Fix IOError when files listed under version control are missing (`issue #32 `__). * Improved wording of the match/do not match messages (`issue #34 `__). * Handle a relative --python path (`issue #36 `__). * Warn about leading and trailing slashes in MANIFEST.in (`issue #37 `__). * Ignore .travis.yml by default (`issue #39 `__). * Suggest a rule for Makefile found deeper in the source tree. 0.21 (2014-06-13) ----------------- * Don't drop setup.cfg when copying version-controlled files into a clean temporary directory (`issue #29 `__). 0.20 (2014-05-14) ----------------- * Restore warning about files included in the sdist but not added to the version control system (`issue #27 `__). * Fix ``check-manifest relative/pathname`` (`issue #28 `__). 0.19 (2014-02-09) ----------------- * More correct MANIFEST.in parsing for exclusion rules. * Some effort was expended towards Windows compatibility. * Handles non-ASCII filenames, as long as they're valid in your locale (`issue #23 `__, `#25 `__). 0.18 (2014-01-30) ----------------- * Friendlier error message when an external command cannot be found (`issue #21 `__). * Add suggestion pattern for `.coveragerc`. * Python 2.6 support (`issue #22 `__). 0.17 (2013-10-10) ----------------- * Read the existing MANIFEST.in file for files to ignore (`issue #19 `__). 0.16 (2013-10-01) ----------------- * Fix Subversion status parsing in the presence of svn usernames longer than 12 characters (`issue #18 `__). 0.15 (2013-09-20) ----------------- * Normalize the paths of all files, avoiding some duplicate misses of directories. (`issue #16 `__). [maurits] 0.14 (2013-08-28) ----------------- * Supports packages that do not live in the root of a version control repository (`issue #15 `__). * More reliable svn support: detect files that have been added but not committed (or committed but not updated). * Licence changed from GPL (v2 or later) to MIT (`issue #12 `__). 0.13 (2013-07-31) ----------------- * New command line option: --ignore (`issue #11 `__). Contributed by Steven Myint. * New command line option: -p, --python. Defaults to the Python you used to run check-manifest. Fixes issues with packages that require Python 3 to run setup.py (`issue #13 `__). 0.12 (2013-05-15) ----------------- * Add suggestion pattern for `Makefile`. * More generic suggestion patterns, should cover almost anything. * zest.releaser_ integration: skip check-release for non-Python packages (`issue #9 `__). 0.11 (2013-03-20) ----------------- * Make sure ``MANIFEST.in`` is not ignored even if it hasn't been added to the VCS yet (`issue #7 `__). 0.10 (2013-03-17) ----------------- * ``check-manifest --version`` now prints the version number. * Don't apologize for not adding rules for directories (especially after adding rules that include files inside that directory). * Python 3 support contributed by Steven Myint. * Default ignore patterns can be configured in ``setup.cfg`` (`issue #3 `_). 0.9 (2013-03-06) ---------------- * Add suggestion pattern for `.travis.yml`. * When check-manifest -u (or -c) doesn't know how to write a rule matching a particular file, it now apologizes explicitly. * Copy the source tree to a temporary directory before running python setup.py sdist to avoid side effects from setuptools plugins or stale \*.egg-info/SOURCES.txt files (`issue #1 `_). * Warn if `*.egg-info` or `*.mo` is actually checked into the VCS. * Don't complain if `*.mo` files are present in the sdist but not in the VCS (`issue #2 `_). 0.8 (2013-03-06) ---------------- * Entry point for zest.releaser_. If you install both zest.releaser and check-manifest, you will be asked if you want to check your manifest during ``fullrelease``. .. _zest.releaser: https://pypi.python.org/pypi/zest.releaser 0.7 (2013-03-05) ---------------- * First release available from the Python Package Index. * Moved from https://gist.github.com/4277075 to https://github.com/mgedmin/check-manifest * Added README.rst, CHANGES.rst, setup.py, tox.ini (but no real tests yet), MANIFEST.in, and a Makefile. * Fixed a bug in error reporting (when setup.py failed, the user would get `TypeError: descriptor '__init__' requires an 'exceptions.Exception' object but received a 'str'`). Keywords: distutils,setuptools,packaging,manifest,checker,linter Platform: UNKNOWN Classifier: Development Status :: 4 - Beta Classifier: Environment :: Console Classifier: Intended Audience :: Developers Classifier: License :: OSI Approved :: MIT License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3 :: Only Classifier: Programming Language :: Python :: 3.6 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: Implementation :: CPython Classifier: Programming Language :: Python :: Implementation :: PyPy Requires-Python: >=3.6 Description-Content-Type: text/x-rst Provides-Extra: test ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609771032.0 check-manifest-0.46/check_manifest.egg-info/SOURCES.txt0000644000175000017500000000067600000000000021707 0ustar00mgmg00000000000000.coveragerc .gitignore .pre-commit-hooks.yaml CHANGES.rst LICENSE.rst MANIFEST.in Makefile README.rst appveyor.yml check_manifest.py release.mk setup.cfg setup.py tests.py tox.ini check_manifest.egg-info/PKG-INFO check_manifest.egg-info/SOURCES.txt check_manifest.egg-info/dependency_links.txt check_manifest.egg-info/entry_points.txt check_manifest.egg-info/not-zip-safe check_manifest.egg-info/requires.txt check_manifest.egg-info/top_level.txt././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609771032.0 check-manifest-0.46/check_manifest.egg-info/dependency_links.txt0000644000175000017500000000000100000000000024060 0ustar00mgmg00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609771032.0 check-manifest-0.46/check_manifest.egg-info/entry_points.txt0000644000175000017500000000022000000000000023302 0ustar00mgmg00000000000000[console_scripts] check-manifest = check_manifest:main [zest.releaser.prereleaser.before] check-manifest = check_manifest:zest_releaser_check ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1560431220.0 check-manifest-0.46/check_manifest.egg-info/not-zip-safe0000644000175000017500000000000100000000000022240 0ustar00mgmg00000000000000 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609771032.0 check-manifest-0.46/check_manifest.egg-info/requires.txt0000644000175000017500000000006600000000000022414 0ustar00mgmg00000000000000build>=0.1 setuptools toml [test] mock>=3.0.0 pytest ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609771032.0 check-manifest-0.46/check_manifest.egg-info/top_level.txt0000644000175000017500000000001700000000000022542 0ustar00mgmg00000000000000check_manifest ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1609770891.0 check-manifest-0.46/check_manifest.py0000775000175000017500000011565000000000000016707 0ustar00mgmg00000000000000#!/usr/bin/env python3 """Check the MANIFEST.in file in a Python source package for completeness. This script works by building a source distribution archive (by running setup.py sdist), then checking the file list in the archive against the file list in version control (Subversion, Git, Mercurial, Bazaar are supported). Since the first check can fail to catch missing MANIFEST.in entries when you've got the right setuptools version control system support plugins installed, the script copies all the versioned files into a temporary directory and builds the source distribution again. This also avoids issues with stale egg-info/SOURCES.txt files that may cause files not mentioned in MANIFEST.in to be included nevertheless. """ import argparse import codecs import configparser import fnmatch import locale import os import posixpath import re import shutil import stat import subprocess import sys import tarfile import tempfile import unicodedata import zipfile from contextlib import contextmanager from typing import List, Optional, Union from xml.etree import ElementTree as ET import toml from setuptools.command.egg_info import translate_pattern # import distutils after setuptools to avoid a warning from distutils.text_file import TextFile # isort:skip __version__ = '0.46' __author__ = 'Marius Gedminas ' __licence__ = 'MIT' __url__ = 'https://github.com/mgedmin/check-manifest' class Failure(Exception): """An expected failure (as opposed to a bug in this script).""" # # User interface # class UI: def __init__(self, verbosity=1): self.verbosity = verbosity self._to_be_continued = False self.stdout = sys.stdout self.stderr = sys.stderr @property def quiet(self): return self.verbosity < 1 @property def verbose(self): return self.verbosity >= 2 def _check_tbc(self): if self._to_be_continued: print(file=self.stdout) self._to_be_continued = False def info(self, message): if self.quiet: return self._check_tbc() print(message, file=self.stdout) def info_begin(self, message): if not self.verbose: return self._check_tbc() print(message, end="", file=self.stdout) self._to_be_continued = True def info_continue(self, message): if not self.verbose: return print(message, end="", file=self.stdout) self._to_be_continued = True def info_end(self, message): if not self.verbose: return print(message, file=self.stdout) self._to_be_continued = False def error(self, message): self._check_tbc() print(message, file=self.stderr) def warning(self, message): self._check_tbc() print(message, file=self.stderr) def format_list(list_of_strings): return "\n".join(" " + s for s in list_of_strings) def format_missing(missing_from_a, missing_from_b, name_a, name_b): res = [] if missing_from_a: res.append("missing from %s:\n%s" % (name_a, format_list(sorted(missing_from_a)))) if missing_from_b: res.append("missing from %s:\n%s" % (name_b, format_list(sorted(missing_from_b)))) return '\n'.join(res) # # Filesystem/OS utilities # class CommandFailed(Failure): def __init__(self, command: List[str], status: int, output: str) -> None: super().__init__("%s failed (status %s):\n%s" % ( command, status, output)) def run( command: List[str], *, encoding: Optional[str] = None, decode: bool = True, cwd: Optional[str] = None # Python 3.5 forbids trailing comma here! ) -> Union[str, bytes]: """Run a command [cmd, arg1, arg2, ...]. Returns the output (stdout only). Raises CommandFailed in cases of error. """ if not encoding: encoding = locale.getpreferredencoding() try: pipe = subprocess.Popen(command, stdin=subprocess.DEVNULL, stdout=subprocess.PIPE, stderr=subprocess.PIPE, cwd=cwd) except OSError as e: raise Failure(f"could not run {command}: {e}") output, stderr = pipe.communicate() status = pipe.wait() if status != 0: raise CommandFailed(command, status, (output + stderr).decode(encoding, 'replace')) if decode: return output.decode(encoding) return output @contextmanager def cd(directory): """Change the current working directory, temporarily. Use as a context manager: with cd(d): ... """ old_dir = os.getcwd() try: os.chdir(directory) yield finally: os.chdir(old_dir) @contextmanager def mkdtemp(hint=''): """Create a temporary directory, then clean it up. Use as a context manager: with mkdtemp('-purpose'): ... """ dirname = tempfile.mkdtemp(prefix='check-manifest-', suffix=hint) try: yield dirname finally: rmtree(dirname) def chmod_plus(path, add_bits): """Change a file's mode by adding a few bits. Like chmod + in a Unix shell. """ try: os.chmod(path, stat.S_IMODE(os.stat(path).st_mode) | add_bits) except OSError: # pragma: nocover pass # well, we tried def rmtree(path): """A version of rmtree that can deal with read-only files and directories. Needed because the stock shutil.rmtree() fails with an access error when there are read-only files in the directory on Windows, or when the directory itself is read-only on Unix. """ def onerror(func, path, exc_info): # Did you know what on Python 3.3 on Windows os.remove() and # os.unlink() are distinct functions? if func is os.remove or func is os.unlink or func is os.rmdir: if sys.platform != 'win32': chmod_plus(os.path.dirname(path), stat.S_IWUSR | stat.S_IXUSR) chmod_plus(path, stat.S_IWUSR) func(path) else: raise shutil.rmtree(path, onerror=onerror) def copy_files(filelist, destdir): """Copy a list of files to destdir, preserving directory structure. File names should be relative to the current working directory. """ for filename in filelist: destfile = os.path.join(destdir, filename) # filename should not be absolute, but let's double-check assert destfile.startswith(destdir + os.path.sep) destfiledir = os.path.dirname(destfile) if not os.path.isdir(destfiledir): os.makedirs(destfiledir) if os.path.isdir(filename): os.mkdir(destfile) else: shutil.copy2(filename, destfile) def get_one_file_in(dirname): """Return the pathname of the one file in a directory. Raises if the directory has no files or more than one file. """ files = os.listdir(dirname) if len(files) > 1: raise Failure('More than one file exists in %s:\n%s' % (dirname, '\n'.join(sorted(files)))) elif not files: raise Failure('No files found in %s' % dirname) return os.path.join(dirname, files[0]) # # File lists are a fundamental data structure here. We want them to have # the following properties: # # - contain Unicode filenames (normalized to NFC on OS X) # - be sorted # - use / as the directory separator # - list only files, but not directories # # We get these file lists from various sources (zip files, tar files, version # control systems) and we have to normalize them into our common format before # comparing. # def canonical_file_list(filelist): """Return the file list convered to a canonical form. This means: - converted to Unicode normal form C, when running on Mac OS X - sorted alphabetically - use / as the directory separator - list files but not directories Caveat: since it works on file lists taken from archives and such, it doesn't know whether a particular filename refers to a file or a directory, unless it finds annother filename that is inside the first one. In other words, canonical_file_list() will not remove the names of empty directories if those appear in the initial file list. """ names = set(normalize_names(filelist)) for name in list(names): while name: name = posixpath.dirname(name) names.discard(name) return sorted(names) def get_sdist_file_list(sdist_filename, ignore): """Return the list of interesting files in a source distribution. Removes extra generated files like PKG-INFO and *.egg-info that are usually present only in the sdist, but not in the VCS. Supports .tar.gz and .zip sdists. """ return strip_sdist_extras( ignore, strip_toplevel_name(get_archive_file_list(sdist_filename))) def get_archive_file_list(archive_filename): """Return the list of files in an archive. Supports .tar.gz and .zip. """ if archive_filename.endswith('.zip'): with zipfile.ZipFile(archive_filename) as zf: filelist = zf.namelist() elif archive_filename.endswith(('.tar.gz', '.tar.bz2', '.tar')): with tarfile.open(archive_filename) as tf: # XXX: is unicodify() necessary now that Py2 is no longer supported? filelist = map(unicodify, tf.getnames()) else: raise Failure('Unrecognized archive type: %s' % os.path.basename(archive_filename)) return canonical_file_list(filelist) def unicodify(filename): """Make sure filename is Unicode. Because the tarfile module on Python 2 doesn't return Unicode. """ if isinstance(filename, bytes): # XXX: Ah, but is it right to use the locale encoding here, or should I # use sys.getfilesystemencoding()? A good question! return filename.decode(locale.getpreferredencoding()) else: return filename def strip_toplevel_name(filelist): """Strip toplevel name from a file list. >>> strip_toplevel_name(['a', 'a/b', 'a/c', 'a/c/d']) ['b', 'c', 'c/d'] >>> strip_toplevel_name(['a', 'a/', 'a/b', 'a/c', 'a/c/d']) ['b', 'c', 'c/d'] >>> strip_toplevel_name(['a/b', 'a/c', 'a/c/d']) ['b', 'c', 'c/d'] """ if not filelist: return filelist prefix = filelist[0] # so here's a function we assume / is the directory separator if '/' in prefix: prefix = prefix.partition('/')[0] + '/' names = filelist else: prefix = prefix + '/' names = filelist[1:] for name in names: if not name.startswith(prefix): raise Failure("File doesn't have the common prefix (%s): %s" % (name, prefix)) return [name[len(prefix):] for name in names if name != prefix] class VCS: def __init__(self, ui): self.ui = ui @classmethod def detect(cls, location): return os.path.isdir(os.path.join(location, cls.metadata_name)) def get_versioned_files(self): raise NotImplementedError('this is an abstract method') class Git(VCS): metadata_name = '.git' # Git for Windows uses UTF-8 instead of the locale encoding. # Git on POSIX systems uses the locale encoding. _encoding = 'UTF-8' if sys.platform == 'win32' else None @classmethod def detect(cls, location): # .git can be a file for submodules return os.path.exists(os.path.join(location, cls.metadata_name)) def _has_submodules(cls): return os.path.exists(".gitmodules") def get_versioned_files(self): """List all files versioned by git in the current directory.""" extra_args = ["--recurse-submodules"] if self._has_submodules() else [] output = run( ["git", "ls-files", "-z"] + extra_args, encoding=self._encoding, ) # -z tells git to use \0 as a line terminator; split() treats it as a # line separator, so we always get one empty line at the end, which we # drop with the [:-1] slice return output.split("\0")[:-1] class Mercurial(VCS): metadata_name = '.hg' def get_versioned_files(self): """List all files under Mercurial control in the current directory.""" output = run(['hg', 'status', '-ncamd', '.']) return output.splitlines() class Bazaar(VCS): metadata_name = '.bzr' @classmethod def _get_terminal_encoding(self): # Python 3.6 lets us name the OEM codepage directly, which is lucky # because it also breaks our old method of OEM codepage detection # (PEP-528 changed sys.stdout.encoding to UTF-8). try: codecs.lookup('oem') except LookupError: pass else: # pragma: nocover return 'oem' # Based on bzrlib.osutils.get_terminal_encoding() encoding = getattr(sys.stdout, 'encoding', None) if not encoding: encoding = getattr(sys.stdin, 'encoding', None) if encoding == 'cp0': # "no codepage" encoding = None # NB: bzrlib falls back on bzrlib.osutils.get_user_encoding(), # which is like locale.getpreferredencoding() on steroids, and # also includes a fallback from 'ascii' to 'utf-8' when # sys.platform is 'darwin'. This is probably something we might # want to do in run(), but I'll wait for somebody to complain # first, since I don't have a Mac OS X machine and cannot test. return encoding def get_versioned_files(self): """List all files versioned in Bazaar in the current directory.""" encoding = self._get_terminal_encoding() output = run(['bzr', 'ls', '-VR'], encoding=encoding) return output.splitlines() class Subversion(VCS): metadata_name = '.svn' def get_versioned_files(self): """List all files under SVN control in the current directory.""" output = run(['svn', 'st', '-vq', '--xml'], decode=False) tree = ET.XML(output) return sorted(entry.get('path') for entry in tree.findall('.//entry') if self.is_interesting(entry)) def is_interesting(self, entry): """Is this entry interesting? ``entry`` is an XML node representing one entry of the svn status XML output. It looks like this:: mg 2015-02-06T07:52:38.163516Z """ if entry.get('path') == '.': return False status = entry.find('wc-status') if status is None: self.ui.warning( 'svn status --xml parse error: without' ' ' % entry.get('path') ) return False # For SVN externals we get two entries: one mentioning the # existence of the external, and one about the status of the external. if status.get('item') in ('unversioned', 'external'): return False return True def detect_vcs(ui): """Detect the version control system used for the current directory.""" location = os.path.abspath('.') while True: for vcs in Git, Mercurial, Bazaar, Subversion: if vcs.detect(location): return vcs(ui) parent = os.path.dirname(location) if parent == location: raise Failure("Couldn't find version control data" " (git/hg/bzr/svn supported)") location = parent def get_vcs_files(ui): """List all files under version control in the current directory.""" vcs = detect_vcs(ui) return canonical_file_list(vcs.get_versioned_files()) def normalize_names(names): """Normalize file names.""" return [normalize_name(name) for name in names] def normalize_name(name): """Some VCS print directory names with trailing slashes. Strip them. Easiest is to normalize the path. And encodings may trip us up too, especially when comparing lists of files. Plus maybe lowercase versus uppercase. """ name = os.path.normpath(name).replace(os.path.sep, '/') name = unicodify(name) # XXX is this necessary? if sys.platform == 'darwin': # Mac OS X may have problems comparing non-ASCII filenames, so # we convert them. name = unicodedata.normalize('NFC', name) return name # # Packaging logic # class IgnoreList: def __init__(self): self._regexps = [] @classmethod def default(cls): return ( cls() # these are always generated .global_exclude('PKG-INFO') .global_exclude('*.egg-info/*') # setup.cfg is always generated, but sometimes also kept in source control .global_exclude('setup.cfg') # it's not a problem if the sdist is lacking these files: .global_exclude( '.hgtags', '.hgsigs', '.hgignore', '.gitignore', '.bzrignore', '.gitattributes', ) # GitHub template files .prune('.github') # we can do without these in sdists .global_exclude('.travis.yml') .global_exclude('Jenkinsfile') # It's convenient to ship compiled .mo files in sdists, but they # shouldn't be checked in, so don't complain that they're missing # from VCS .global_exclude('*.mo') ) def clear(self): self._regexps = [] def __repr__(self): return 'IgnoreList(%r)' % (self._regexps) def __eq__(self, other): return isinstance(other, IgnoreList) and self._regexps == other._regexps def __iadd__(self, other): assert isinstance(other, IgnoreList) self._regexps += other._regexps return self def _path(self, path): return path.replace('/', os.path.sep) def exclude(self, *patterns): for pat in patterns: pat = self._path(pat) self._regexps.append(translate_pattern(pat)) return self def global_exclude(self, *patterns): for pat in patterns: pat = os.path.join('**', self._path(pat)) self._regexps.append(translate_pattern(pat)) return self def recursive_exclude(self, dirname, *patterns): dirname = self._path(dirname) for pat in patterns: pat = os.path.join(dirname, '**', self._path(pat)) self._regexps.append(translate_pattern(pat)) return self def prune(self, subdir): pat = os.path.join(self._path(subdir), '**') self._regexps.append(translate_pattern(pat)) return self def filter(self, filelist): return [name for name in filelist if not any(rx.match(self._path(name)) for rx in self._regexps)] WARN_ABOUT_FILES_IN_VCS = [ # generated files should not be committed into the VCS 'PKG-INFO', '*.egg-info', '*.mo', '*.py[co]', '*.so', '*.pyd', '*~', '.*.sw[po]', '.#*', ] SUGGESTIONS = [(re.compile(pattern), suggestion) for pattern, suggestion in [ # regexp -> suggestion ('^([^/]+[.](cfg|ini))$', r'include \1'), ('^([.]travis[.]yml)$', r'include \1'), ('^([.]coveragerc)$', r'include \1'), ('^([A-Z]+)$', r'include \1'), ('^(Makefile)$', r'include \1'), ('^[^/]+[.](txt|rst|py)$', r'include *.\1'), ('^([a-zA-Z_][a-zA-Z_0-9]*)/' '.*[.](py|zcml|pt|mako|xml|html|txt|rst|css|png|jpg|dot|po|pot|mo|ui|desktop|bat)$', r'recursive-include \1 *.\2'), ('^([a-zA-Z_][a-zA-Z_0-9]*)(?:/.*)?/(Makefile)$', r'recursive-include \1 \2'), # catch-all rules that actually cover some of the above; somewhat # experimental: I fear false positives ('^([a-zA-Z_0-9]+)$', r'include \1'), ('^[^/]+[.]([a-zA-Z_0-9]+)$', r'include *.\1'), ('^([a-zA-Z_][a-zA-Z_0-9]*)/.*[.]([a-zA-Z_0-9]+)$', r'recursive-include \1 *.\2'), ]] CFG_SECTION_CHECK_MANIFEST = 'check-manifest' CFG_IGNORE_DEFAULT_RULES = (CFG_SECTION_CHECK_MANIFEST, 'ignore-default-rules') CFG_IGNORE = (CFG_SECTION_CHECK_MANIFEST, 'ignore') CFG_IGNORE_BAD_IDEAS = (CFG_SECTION_CHECK_MANIFEST, 'ignore-bad-ideas') def read_config(): """Read configuration from file if possible.""" ignore = IgnoreList.default() ignore_bad_ideas = IgnoreList() config = _load_config() if config.get(CFG_IGNORE_DEFAULT_RULES[1], False): ignore.clear() if CFG_IGNORE[1] in config: for p in config[CFG_IGNORE[1]]: if p: ignore.global_exclude(p) if CFG_IGNORE_BAD_IDEAS[1] in config: for p in config[CFG_IGNORE_BAD_IDEAS[1]]: if p: ignore_bad_ideas.global_exclude(p) return ignore, ignore_bad_ideas def _load_config(): """Searches for config files, reads them and returns a dictionary Looks for a ``check-manifest`` section in ``pyproject.toml``, ``setup.cfg``, and ``tox.ini``, in that order. The first file that exists and has that section will be loaded and returned as a dictionary. """ if os.path.exists("pyproject.toml"): config = toml.load("pyproject.toml") if CFG_SECTION_CHECK_MANIFEST in config.get("tool", {}): return config["tool"][CFG_SECTION_CHECK_MANIFEST] search_files = ['setup.cfg', 'tox.ini'] config_parser = configparser.ConfigParser() for filename in search_files: if (config_parser.read([filename]) and config_parser.has_section(CFG_SECTION_CHECK_MANIFEST)): config = {} if config_parser.has_option(*CFG_IGNORE_DEFAULT_RULES): ignore_defaults = config_parser.getboolean(*CFG_IGNORE_DEFAULT_RULES) config[CFG_IGNORE_DEFAULT_RULES[1]] = ignore_defaults if config_parser.has_option(*CFG_IGNORE): patterns = [ p.strip() for p in config_parser.get(*CFG_IGNORE).splitlines() ] config[CFG_IGNORE[1]] = patterns if config_parser.has_option(*CFG_IGNORE_BAD_IDEAS): patterns = [ p.strip() for p in config_parser.get(*CFG_IGNORE_BAD_IDEAS).splitlines() ] config[CFG_IGNORE_BAD_IDEAS[1]] = patterns return config return {} def read_manifest(ui): """Read existing configuration from MANIFEST.in. We use that to ignore anything the MANIFEST.in ignores. """ if not os.path.isfile('MANIFEST.in'): return IgnoreList() return _get_ignore_from_manifest('MANIFEST.in', ui) def _get_ignore_from_manifest(filename, ui): """Gather the various ignore patterns from a MANIFEST.in. Returns an IgnoreList instance. """ class MyTextFile(TextFile): def error(self, msg, line=None): # pragma: nocover # (this is never called by TextFile in current versions of CPython) raise Failure(self.gen_error(msg, line)) def warn(self, msg, line=None): ui.warning(self.gen_error(msg, line)) template = MyTextFile(filename, strip_comments=True, skip_blanks=True, join_lines=True, lstrip_ws=True, rstrip_ws=True, collapse_join=True) try: lines = template.readlines() finally: template.close() return _get_ignore_from_manifest_lines(lines, ui) def _get_ignore_from_manifest_lines(lines, ui): """Gather the various ignore patterns from a MANIFEST.in. 'lines' should be a list of strings with comments removed and continuation lines joined. Returns an IgnoreList instance. """ ignore = IgnoreList() for line in lines: try: cmd, rest = line.split(None, 1) except ValueError: # no whitespace, so not interesting continue for part in rest.split(): # distutils enforces these warnings on Windows only if part.startswith('/'): ui.warning("ERROR: Leading slashes are not allowed in MANIFEST.in on Windows: %s" % part) if part.endswith('/'): ui.warning("ERROR: Trailing slashes are not allowed in MANIFEST.in on Windows: %s" % part) if cmd == 'exclude': ignore.exclude(*rest.split()) elif cmd == 'global-exclude': ignore.global_exclude(*rest.split()) elif cmd == 'recursive-exclude': try: dirname, patterns = rest.split(None, 1) except ValueError: # Wrong MANIFEST.in line. ui.warning( "You have a wrong line in MANIFEST.in: %r\n" "'recursive-exclude' expects ..." % line ) continue ignore.recursive_exclude(dirname, *patterns.split()) elif cmd == 'prune': ignore.prune(rest) # XXX: This ignores all 'include'/'global-include'/'recusive-include'/'graft' commands, # which is wrong! Quoting the documentation: # # The order of commands in the manifest template matters: initially, # we have the list of default files as described above, and each # command in the template adds to or removes from that list of # files. # -- https://docs.python.org/3.8/distutils/sourcedist.html#specifying-the-files-to-distribute return ignore def file_matches(filename, patterns): """Does this filename match any of the patterns?""" return any(fnmatch.fnmatch(filename, pat) or fnmatch.fnmatch(os.path.basename(filename), pat) for pat in patterns) def strip_sdist_extras(ignore, filelist): """Strip generated files that are only present in source distributions. We also strip files that are ignored for other reasons, like command line arguments, setup.cfg rules or MANIFEST.in rules. """ return ignore.filter(filelist) def find_bad_ideas(filelist): """Find files matching WARN_ABOUT_FILES_IN_VCS patterns.""" return [name for name in filelist if file_matches(name, WARN_ABOUT_FILES_IN_VCS)] def find_suggestions(filelist): """Suggest MANIFEST.in patterns for missing files. Returns two lists: one with suggested MANIGEST.in commands, and one with files for which no suggestions were offered. """ suggestions = set() unknowns = [] for filename in filelist: for pattern, suggestion in SUGGESTIONS: m = pattern.match(filename) if m is not None: suggestions.add(pattern.sub(suggestion, filename)) break else: unknowns.append(filename) return sorted(suggestions), unknowns def is_package(source_tree='.'): """Is the directory the root of a Python package? Note: the term "package" here refers to a collection of files with a setup.py/pyproject.toml, not to a directory with an __init__.py. """ return ( os.path.exists(os.path.join(source_tree, 'setup.py')) or os.path.exists(os.path.join(source_tree, 'pyproject.toml')) ) def extract_version_from_filename(filename): """Extract version number from sdist filename.""" filename = os.path.splitext(os.path.basename(filename))[0] if filename.endswith('.tar'): filename = os.path.splitext(filename)[0] return filename.partition('-')[2] def should_use_pep_517(): """Check if the project uses PEP-517 builds.""" # https://www.python.org/dev/peps/pep-0517/#build-system-table says # "If the pyproject.toml file is absent, or the build-backend key is # missing, the source tree is not using this specification, and tools # should revert to the legacy behaviour of running setup.py". if not os.path.exists('pyproject.toml'): return False config = toml.load("pyproject.toml") if "build-system" not in config: return False if "build-backend" not in config["build-system"]: return False return True def build_sdist(tempdir, python=sys.executable, build_isolation=True): """Build a source distribution in a temporary directory. Should be run with the current working directory inside the Python package you want to build. """ if should_use_pep_517(): # I could do this in-process with # import build.__main__ # build.__main__.build('.', tempdir) # but then it would print a bunch of things to stdout and I'd have to # worry about exceptions cmd = [python, '-m', 'build', '--sdist', '.', '--outdir', tempdir] if not build_isolation: cmd.append('--no-isolation') run(cmd) else: run([python, 'setup.py', 'sdist', '-d', tempdir]) def check_manifest(source_tree='.', create=False, update=False, python=sys.executable, ui=None, extra_ignore=None, extra_ignore_bad_ideas=None, build_isolation=True): """Compare a generated source distribution with list of files in a VCS. Returns True if the manifest is fine. """ if ui is None: ui = UI() all_ok = True if os.path.sep in python: python = os.path.abspath(python) with cd(source_tree): if not is_package(): raise Failure( 'This is not a Python project (no setup.py/pyproject.toml).') ignore, ignore_bad_ideas = read_config() ignore += read_manifest(ui) if extra_ignore: ignore += extra_ignore if extra_ignore_bad_ideas: ignore_bad_ideas += extra_ignore_bad_ideas ui.info_begin("listing source files under version control") all_source_files = get_vcs_files(ui) source_files = strip_sdist_extras(ignore, all_source_files) ui.info_continue(": %d files and directories" % len(source_files)) if not all_source_files: raise Failure('There are no files added to version control!') ui.info_begin("building an sdist") with mkdtemp('-sdist') as tempdir: build_sdist(tempdir, python=python, build_isolation=build_isolation) sdist_filename = get_one_file_in(tempdir) ui.info_continue(": %s" % os.path.basename(sdist_filename)) sdist_files = get_sdist_file_list(sdist_filename, ignore) ui.info_continue(": %d files and directories" % len(sdist_files)) version = extract_version_from_filename(sdist_filename) existing_source_files = list(filter(os.path.exists, all_source_files)) missing_source_files = sorted(set(all_source_files) - set(existing_source_files)) if missing_source_files: ui.warning("some files listed as being under source control are missing:\n%s" % format_list(missing_source_files)) ui.info_begin("copying source files to a temporary directory") with mkdtemp('-sources') as tempsourcedir: copy_files(existing_source_files, tempsourcedir) for filename in 'MANIFEST.in', 'setup.py', 'pyproject.toml': if filename not in source_files and os.path.exists(filename): # See https://github.com/mgedmin/check-manifest/issues/7 # and https://github.com/mgedmin/check-manifest/issues/46: # if we do this, the user gets a warning about files # missing from source control; if we don't do this, # things get very confusing for the user! copy_files([filename], tempsourcedir) ui.info_begin("building a clean sdist") with cd(tempsourcedir): with mkdtemp('-sdist') as tempdir: os.environ['SETUPTOOLS_SCM_PRETEND_VERSION'] = version build_sdist(tempdir, python=python, build_isolation=build_isolation) sdist_filename = get_one_file_in(tempdir) ui.info_continue(": %s" % os.path.basename(sdist_filename)) clean_sdist_files = get_sdist_file_list(sdist_filename, ignore) ui.info_continue(": %d files and directories" % len(clean_sdist_files)) missing_from_manifest = set(source_files) - set(clean_sdist_files) missing_from_VCS = set(sdist_files + clean_sdist_files) - set(source_files) if not missing_from_manifest and not missing_from_VCS: ui.info("lists of files in version control and sdist match") else: ui.error( "lists of files in version control and sdist do not match!\n%s" % format_missing(missing_from_VCS, missing_from_manifest, "VCS", "sdist")) suggestions, unknowns = find_suggestions(missing_from_manifest) user_asked_for_help = update or (create and not os.path.exists('MANIFEST.in')) if 'MANIFEST.in' not in existing_source_files: if suggestions and not user_asked_for_help: ui.info("no MANIFEST.in found; you can run 'check-manifest -c' to create one") else: ui.info("no MANIFEST.in found") if suggestions: ui.info("suggested MANIFEST.in rules:\n%s" % format_list(suggestions)) if user_asked_for_help: existed = os.path.exists('MANIFEST.in') with open('MANIFEST.in', 'a') as f: if not existed: ui.info("creating MANIFEST.in") else: ui.info("updating MANIFEST.in") f.write('\n# added by check-manifest\n') f.write('\n'.join(suggestions) + '\n') if unknowns: ui.info("don't know how to come up with rules matching\n%s" % format_list(unknowns)) elif user_asked_for_help: ui.info("don't know how to come up with rules matching any of the files, sorry!") all_ok = False bad_ideas = find_bad_ideas(all_source_files) filtered_bad_ideas = ignore_bad_ideas.filter(bad_ideas) if filtered_bad_ideas: ui.warning( "you have %s in source control!\n" "that's a bad idea: auto-generated files should not be versioned" % filtered_bad_ideas[0]) if len(filtered_bad_ideas) > 1: ui.warning("this also applies to the following:\n%s" % format_list(filtered_bad_ideas[1:])) all_ok = False return all_ok # # Main script # def main(): parser = argparse.ArgumentParser( description="Check a Python MANIFEST.in file for completeness", formatter_class=argparse.ArgumentDefaultsHelpFormatter) parser.add_argument('source_tree', default='.', nargs='?', help='location for the source tree') parser.add_argument('--version', action='version', version='%(prog)s version ' + __version__) parser.add_argument('-q', '--quiet', action='store_const', dest='quiet', const=0, default=1, help='reduced output verbosity') parser.add_argument('-v', '--verbose', action='store_const', dest='verbose', const=1, default=0, help='more verbose output') parser.add_argument('-c', '--create', action='store_true', help='create a MANIFEST.in if missing') parser.add_argument('-u', '--update', action='store_true', help='append suggestions to MANIFEST.in (implies --create)') parser.add_argument('-p', '--python', default=sys.executable, help='use this Python interpreter for running setup.py sdist') parser.add_argument('--ignore', metavar='patterns', default=None, help='ignore files/directories matching these' ' comma-separated patterns') parser.add_argument('--ignore-bad-ideas', metavar='patterns', default=[], help='ignore bad idea files/directories ' 'matching these comma-separated patterns') parser.add_argument( '--no-build-isolation', dest='build_isolation', action='store_false', help='Disable isolation when building a modern source distribution. ' 'Build dependencies specified by PEP 518 must be already installed if ' 'this option is used.', ) args = parser.parse_args() ignore = IgnoreList() if args.ignore: ignore.global_exclude(*args.ignore.split(',')) ignore_bad_ideas = IgnoreList() if args.ignore_bad_ideas: ignore_bad_ideas.global_exclude(*args.ignore_bad_ideas.split(',')) ui = UI(verbosity=args.quiet + args.verbose) try: if not check_manifest(args.source_tree, create=args.create, update=args.update, python=args.python, ui=ui, extra_ignore=ignore, extra_ignore_bad_ideas=ignore_bad_ideas, build_isolation=args.build_isolation): sys.exit(1) except Failure as e: ui.error(str(e)) sys.exit(2) # # zest.releaser integration # def zest_releaser_check(data): """Check the completeness of MANIFEST.in before the release. This is an entry point for zest.releaser. See the documentation at https://zestreleaser.readthedocs.io/en/latest/entrypoints.html """ from zest.releaser.utils import ask source_tree = data['workingdir'] if not is_package(source_tree): # You can use zest.releaser on things that are not Python packages. # It's pointless to run check-manifest in those circumstances. # See https://github.com/mgedmin/check-manifest/issues/9 for details. return if not ask("Do you want to run check-manifest?"): return ui = UI() try: if not check_manifest(source_tree, ui=ui): if not ask("MANIFEST.in has problems." " Do you want to continue despite that?", default=False): sys.exit(1) except Failure as e: ui.error(str(e)) if not ask("Something bad happened." " Do you want to continue despite that?", default=False): sys.exit(2) if __name__ == '__main__': main() ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1606847974.0 check-manifest-0.46/release.mk0000664000175000017500000001243100000000000015331 0ustar00mgmg00000000000000# release.mk version 2.0 (2020-10-11) # # Helpful Makefile rules for releasing Python packages. # https://github.com/mgedmin/python-project-skel # You might want to change these FILE_WITH_VERSION ?= setup.py FILE_WITH_CHANGELOG ?= CHANGES.rst CHANGELOG_DATE_FORMAT ?= %Y-%m-%d CHANGELOG_FORMAT ?= $(changelog_ver) ($(changelog_date)) DISTCHECK_DIFF_OPTS ?= $(DISTCHECK_DIFF_DEFAULT_OPTS) # These should be fine PYTHON ?= python3 PYPI_PUBLISH ?= rm -rf dist && $(PYTHON) setup.py -q sdist bdist_wheel && twine check dist/* && twine upload dist/* LATEST_RELEASE_MK_URL = https://raw.githubusercontent.com/mgedmin/python-project-skel/master/release.mk DISTCHECK_DIFF_DEFAULT_OPTS = -x PKG-INFO -x setup.cfg -x '*.egg-info' -x .github -I'^\#' # These should be fine, as long as you use Git VCS_GET_LATEST ?= git pull VCS_STATUS ?= git status --porcelain VCS_EXPORT ?= git archive --format=tar --prefix=tmp/tree/ HEAD | tar -xf - VCS_TAG ?= git tag -s $(changelog_ver) -m \"Release $(changelog_ver)\" VCS_COMMIT_AND_PUSH ?= git commit -av -m "Post-release version bump" && git push && git push --tags # These are internal implementation details changelog_ver = `$(PYTHON) setup.py --version` changelog_date = `LC_ALL=C date +'$(CHANGELOG_DATE_FORMAT)'` # Tweaking the look of 'make help'; most of these are awk literals and need the quotes HELP_INDENT = "" HELP_PREFIX = "make " HELP_WIDTH = 24 HELP_SEPARATOR = " \# " HELP_SECTION_SEP = "\n" .PHONY: help help: @grep -Eh -e '^[a-zA-Z0-9_ -]+:.*?##: .*$$' -e '^##:' $(MAKEFILE_LIST) \ | awk 'BEGIN {FS = "(^|:[^#]*)##: "; section=""}; \ /^##:/ {printf "%s%s\n%s", section, $$2, $(HELP_SECTION_SEP); section=$(HELP_SECTION_SEP)} \ /^[^#]/ {printf "%s\033[36m%-$(HELP_WIDTH)s\033[0m%s%s\n", \ $(HELP_INDENT), $(HELP_PREFIX) $$1, $(HELP_SEPARATOR), $$2}' .PHONY: dist dist: $(PYTHON) setup.py -q sdist bdist_wheel # Provide a default 'make check' to be the same as 'make test', since that's # what 80% of my projects use, but make it possible to override. Now # overriding Make rules is painful, so instead of a regular rule definition # you'll have to override the check_recipe macro. .PHONY: check check: $(check_recipe) ifndef check_recipe define check_recipe = @$(MAKE) test endef endif .PHONY: distcheck distcheck: distcheck-vcs distcheck-sdist .PHONY: distcheck-vcs distcheck-vcs: ifndef FORCE # Bit of a chicken-and-egg here, but if the tree is unclean, make # distcheck-sdist will fail. @test -z "`$(VCS_STATUS) 2>&1`" || { echo; echo "Your working tree is not clean:" 1>&2; $(VCS_STATUS) 1>&2; exit 1; } endif # NB: do not use $(MAKE) in rules with multiple shell commands joined by && # because then make -n distcheck will actually run those instead of just # printing what it does # TBH this could (and probably should) be replaced by check-manifest .PHONY: distcheck-sdist distcheck-sdist: dist pkg_and_version=`$(PYTHON) setup.py --name`-`$(PYTHON) setup.py --version` && \ rm -rf tmp && \ mkdir tmp && \ $(VCS_EXPORT) && \ cd tmp && \ tar -xzf ../dist/$$pkg_and_version.tar.gz && \ diff -ur $$pkg_and_version tree $(DISTCHECK_DIFF_OPTS) && \ cd $$pkg_and_version && \ make dist check && \ cd .. && \ mkdir one two && \ cd one && \ tar -xzf ../../dist/$$pkg_and_version.tar.gz && \ cd ../two/ && \ tar -xzf ../$$pkg_and_version/dist/$$pkg_and_version.tar.gz && \ cd .. && \ diff -ur one two -x SOURCES.txt -I'^#:' && \ cd .. && \ rm -rf tmp && \ echo "sdist seems to be ok" .PHONY: check-latest-rules check-latest-rules: ifndef FORCE @curl -s $(LATEST_RELEASE_MK_URL) | cmp -s release.mk || { printf "\nYour release.mk does not match the latest version at\n$(LATEST_RELEASE_MK_URL)\n\n" 1>&2; exit 1; } endif .PHONY: check-latest-version check-latest-version: $(VCS_GET_LATEST) .PHONY: check-version-number check-version-number: @$(PYTHON) setup.py --version | grep -qv dev || { \ echo "Please remove the 'dev' suffix from the version number in $(FILE_WITH_VERSION)"; exit 1; } .PHONY: check-long-description check-long-description: @$(PYTHON) setup.py --long-description | rst2html --exit-status=2 > /dev/null .PHONY: check-changelog check-changelog: @ver_and_date="$(CHANGELOG_FORMAT)" && \ grep -q "^$$ver_and_date$$" $(FILE_WITH_CHANGELOG) || { \ echo "$(FILE_WITH_CHANGELOG) has no entry for $$ver_and_date"; exit 1; } # NB: the Makefile that includes release.mk may want to add additional # dependencies to the releasechecklist target, but I want 'make distcheck' to # happen last, so that's why I put it into the recipe and not at the end of the # list of dependencies. .PHONY: releasechecklist releasechecklist: check-latest-rules check-latest-version check-version-number check-long-description check-changelog $(MAKE) distcheck .PHONY: release release: releasechecklist do-release ##: prepare a new PyPI release .PHONY: do-release do-release: $(release_recipe) ifndef release_recipe define release_recipe = # I'm chicken so I won't actually do these things yet @echo "Please run" @echo @echo " $(PYPI_PUBLISH)" @echo " $(VCS_TAG)" @echo @echo "Please increment the version number in $(FILE_WITH_VERSION)" @echo "and add a new empty entry at the top of the changelog in $(FILE_WITH_CHANGELOG), then" @echo @echo ' $(VCS_COMMIT_AND_PUSH)' @echo endef endif ././@PaxHeader0000000000000000000000000000003400000000000011452 xustar000000000000000028 mtime=1609771032.8072078 check-manifest-0.46/setup.cfg0000664000175000017500000000113400000000000015177 0ustar00mgmg00000000000000[tool:pytest] norecursedirs = dist build tmp .* *.egg-info python_files = tests.py check_manifest.py addopts = --doctest-modules --ignore=setup.py [zest.releaser] python-file-with-version = check_manifest.py [flake8] ignore = E241,E501,E261,E126,E127,E128,E302,W503 [mypy-setuptools.command.egg_info] ignore_missing_imports = true [mypy-zest.releaser.utils] ignore_missing_imports = true [isort] multi_line_output = 3 include_trailing_comma = true lines_after_imports = 2 reverse_relative = true default_section = THIRDPARTY known_first_party = check_manifest [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608651454.0 check-manifest-0.46/setup.py0000775000175000017500000000455700000000000015107 0ustar00mgmg00000000000000#!/usr/bin/env python import ast import email.utils import os import re from setuptools import setup here = os.path.dirname(__file__) with open(os.path.join(here, 'README.rst')) as readme: with open(os.path.join(here, 'CHANGES.rst')) as changelog: long_description = readme.read() + '\n\n' + changelog.read() metadata = {} with open(os.path.join(here, 'check_manifest.py')) as f: rx = re.compile('(__version__|__author__|__url__|__licence__) = (.*)') for line in f: m = rx.match(line) if m: metadata[m.group(1)] = ast.literal_eval(m.group(2)) version = metadata['__version__'] author, author_email = email.utils.parseaddr(metadata['__author__']) url = metadata['__url__'] licence = metadata['__licence__'] setup( name='check-manifest', version=version, author=author, author_email=author_email, url=url, description='Check MANIFEST.in in a Python source package for completeness', long_description=long_description, long_description_content_type='text/x-rst', keywords=['distutils', 'setuptools', 'packaging', 'manifest', 'checker', 'linter'], classifiers=[ 'Development Status :: 4 - Beta', 'Environment :: Console', 'Intended Audience :: Developers', 'License :: OSI Approved :: MIT License', 'Operating System :: OS Independent', 'Programming Language :: Python', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3 :: Only', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: Implementation :: CPython', 'Programming Language :: Python :: Implementation :: PyPy', ], license=licence, py_modules=['check_manifest'], zip_safe=False, python_requires=">=3.6", install_requires=[ 'build>=0.1', 'setuptools', 'toml', ], extras_require={ 'test': [ 'mock >= 3.0.0', 'pytest', ], }, entry_points={ 'console_scripts': [ 'check-manifest = check_manifest:main', ], 'zest.releaser.prereleaser.before': [ 'check-manifest = check_manifest:zest_releaser_check', ], }, ) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608651454.0 check-manifest-0.46/tests.py0000664000175000017500000020243000000000000015074 0ustar00mgmg00000000000000import codecs import locale import os import posixpath import shutil import subprocess import sys import tarfile import tempfile import textwrap import unittest import zipfile from contextlib import closing from functools import partial from io import BytesIO, StringIO from typing import Optional from xml.etree import ElementTree as ET import mock from check_manifest import rmtree CAN_SKIP_TESTS = os.getenv('SKIP_NO_TESTS', '') == '' try: codecs.lookup('oem') except LookupError: HAS_OEM_CODEC = False else: # Python >= 3.6 on Windows HAS_OEM_CODEC = True class MockUI: def __init__(self, verbosity=1): self.verbosity = verbosity self.warnings = [] self.errors = [] def info(self, message): pass def info_begin(self, message): pass def info_cont(self, message): pass def info_end(self, message): pass def warning(self, message): self.warnings.append(message) def error(self, message): self.errors.append(message) class Tests(unittest.TestCase): def setUp(self): self.ui = MockUI() def make_temp_dir(self): tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest') self.addCleanup(rmtree, tmpdir) return tmpdir def create_file(self, filename, contents): with open(filename, 'w') as f: f.write(contents) def create_zip_file(self, filename, filenames): with closing(zipfile.ZipFile(filename, 'w')) as zf: for fn in filenames: zf.writestr(fn, '') def create_tar_file(self, filename, filenames): with closing(tarfile.TarFile(filename, 'w')) as tf: for fn in filenames: tf.addfile(tarfile.TarInfo(fn), BytesIO()) def test_run_success(self): from check_manifest import run self.assertEqual(run(["true"]), "") def test_run_failure(self): from check_manifest import CommandFailed, run with self.assertRaises(CommandFailed) as cm: run(["false"]) self.assertEqual(str(cm.exception), "['false'] failed (status 1):\n") def test_run_no_such_program(self): from check_manifest import Failure, run with self.assertRaises(Failure) as cm: run(["there-is-really-no-such-program"]) # Linux says "[Errno 2] No such file or directory" # Windows says "[Error 2] The system cannot find the file specified" # but on 3.x it's "[WinErr 2] The system cannot find the file specified" should_start_with = "could not run ['there-is-really-no-such-program']:" self.assertTrue( str(cm.exception).startswith(should_start_with), '\n%r does not start with\n%r' % (str(cm.exception), should_start_with)) def test_mkdtemp_readonly_files(self): from check_manifest import mkdtemp with mkdtemp(hint='-test-readonly') as d: fn = os.path.join(d, 'file.txt') with open(fn, 'w'): pass os.chmod(fn, 0o444) # readonly assert not os.path.exists(d) @unittest.skipIf(sys.platform == 'win32', "No POSIX-like unreadable directories on Windows") def test_rmtree_unreadable_directories(self): d = self.make_temp_dir() sd = os.path.join(d, 'subdir') os.mkdir(sd) os.chmod(sd, 0) # a bad mode for a directory, oops # The onerror API of shutil.rmtree doesn't let us recover from # os.listdir() failures. with self.assertRaises(OSError): rmtree(sd) os.chmod(sd, 0o755) # so we can clean up def test_rmtree_readonly_directories(self): d = self.make_temp_dir() sd = os.path.join(d, 'subdir') fn = os.path.join(sd, 'file.txt') os.mkdir(sd) open(fn, 'w').close() os.chmod(sd, 0o444) # a bad mode for a directory, oops rmtree(sd) assert not os.path.exists(sd) def test_rmtree_readonly_directories_and_files(self): d = self.make_temp_dir() sd = os.path.join(d, 'subdir') fn = os.path.join(sd, 'file.txt') os.mkdir(sd) open(fn, 'w').close() os.chmod(fn, 0o444) # readonly os.chmod(sd, 0o444) # a bad mode for a directory, oops rmtree(sd) assert not os.path.exists(sd) def test_copy_files(self): from check_manifest import copy_files actions = [] n = os.path.normpath with mock.patch('os.path.isdir', lambda d: d in ('b', n('/dest/dir'))): with mock.patch('os.makedirs', lambda d: actions.append('makedirs %s' % d)): with mock.patch('os.mkdir', lambda d: actions.append('mkdir %s' % d)): with mock.patch('shutil.copy2', lambda s, d: actions.append(f'cp {s} {d}')): copy_files(['a', 'b', n('c/d/e')], n('/dest/dir')) self.assertEqual( actions, [ 'cp a %s' % n('/dest/dir/a'), 'mkdir %s' % n('/dest/dir/b'), 'makedirs %s' % n('/dest/dir/c/d'), 'cp %s %s' % (n('c/d/e'), n('/dest/dir/c/d/e')), ]) def test_get_one_file_in(self): from check_manifest import get_one_file_in with mock.patch('os.listdir', lambda dir: ['a']): self.assertEqual(get_one_file_in(os.path.normpath('/some/dir')), os.path.normpath('/some/dir/a')) def test_get_one_file_in_empty_directory(self): from check_manifest import Failure, get_one_file_in with mock.patch('os.listdir', lambda dir: []): with self.assertRaises(Failure) as cm: get_one_file_in('/some/dir') self.assertEqual(str(cm.exception), "No files found in /some/dir") def test_get_one_file_in_too_many(self): from check_manifest import Failure, get_one_file_in with mock.patch('os.listdir', lambda dir: ['b', 'a']): with self.assertRaises(Failure) as cm: get_one_file_in('/some/dir') self.assertEqual(str(cm.exception), "More than one file exists in /some/dir:\na\nb") def test_unicodify(self): from check_manifest import unicodify nonascii = "\u00E9.txt" self.assertEqual(unicodify(nonascii), nonascii) self.assertEqual( unicodify(nonascii.encode(locale.getpreferredencoding())), nonascii) def test_get_archive_file_list_unrecognized_archive(self): from check_manifest import Failure, get_archive_file_list with self.assertRaises(Failure) as cm: get_archive_file_list('/path/to/archive.rar') self.assertEqual(str(cm.exception), 'Unrecognized archive type: archive.rar') def test_get_archive_file_list_zip(self): from check_manifest import get_archive_file_list filename = os.path.join(self.make_temp_dir(), 'archive.zip') self.create_zip_file(filename, ['a', 'b/c']) self.assertEqual(get_archive_file_list(filename), ['a', 'b/c']) def test_get_archive_file_list_zip_nonascii(self): from check_manifest import get_archive_file_list filename = os.path.join(self.make_temp_dir(), 'archive.zip') nonascii = "\u00E9.txt" self.create_zip_file(filename, [nonascii]) self.assertEqual(get_archive_file_list(filename), [nonascii]) def test_get_archive_file_list_tar(self): from check_manifest import get_archive_file_list filename = os.path.join(self.make_temp_dir(), 'archive.tar') self.create_tar_file(filename, ['a', 'b/c']) self.assertEqual(get_archive_file_list(filename), ['a', 'b/c']) def test_get_archive_file_list_tar_nonascii(self): from check_manifest import get_archive_file_list filename = os.path.join(self.make_temp_dir(), 'archive.tar') nonascii = "\u00E9.txt" self.create_tar_file(filename, [nonascii]) self.assertEqual(get_archive_file_list(filename), [nonascii]) def test_format_list(self): from check_manifest import format_list self.assertEqual(format_list([]), "") self.assertEqual(format_list(['a']), " a") self.assertEqual(format_list(['a', 'b']), " a\n b") def test_format_missing(self): from check_manifest import format_missing self.assertEqual( format_missing(set(), set(), "1st", "2nd"), "") self.assertEqual( format_missing({"c"}, {"a"}, "1st", "2nd"), "missing from 1st:\n" " c\n" "missing from 2nd:\n" " a") def test_strip_toplevel_name_empty_list(self): from check_manifest import strip_toplevel_name self.assertEqual(strip_toplevel_name([]), []) def test_strip_toplevel_name_no_common_prefix(self): from check_manifest import Failure, strip_toplevel_name self.assertRaises(Failure, strip_toplevel_name, ["a/b", "c/d"]) def test_detect_vcs_no_vcs(self): from check_manifest import Failure, detect_vcs ui = MockUI() with mock.patch('check_manifest.VCS.detect', staticmethod(lambda *a: False)): with mock.patch('check_manifest.Git.detect', staticmethod(lambda *a: False)): with self.assertRaises(Failure) as cm: detect_vcs(ui) self.assertEqual(str(cm.exception), "Couldn't find version control data" " (git/hg/bzr/svn supported)") def test_normalize_names(self): from check_manifest import normalize_names j = os.path.join self.assertEqual(normalize_names(["a", j("b", ""), j("c", "d"), j("e", "f", ""), j("g", "h", "..", "i")]), ["a", "b", "c/d", "e/f", "g/i"]) def test_canonical_file_list(self): from check_manifest import canonical_file_list j = os.path.join self.assertEqual( canonical_file_list(['b', 'a', 'c', j('c', 'd'), j('e', 'f'), 'g', j('g', 'h', 'i', 'j')]), ['a', 'b', 'c/d', 'e/f', 'g/h/i/j']) def test_file_matches(self): from check_manifest import file_matches patterns = ['setup.cfg', '*.egg-info', '*.egg-info/*'] self.assertFalse(file_matches('setup.py', patterns)) self.assertTrue(file_matches('setup.cfg', patterns)) self.assertTrue(file_matches('src/zope.foo.egg-info', patterns)) self.assertTrue(file_matches('src/zope.foo.egg-info/SOURCES.txt', patterns)) def test_strip_sdist_extras(self): from check_manifest import ( IgnoreList, canonical_file_list, strip_sdist_extras, ) filelist = canonical_file_list([ '.github', '.github/ISSUE_TEMPLATE', '.github/ISSUE_TEMPLATE/bug_report.md', '.gitignore', '.travis.yml', 'setup.py', 'setup.cfg', 'README.txt', 'src', 'src/.gitignore', 'src/zope', 'src/zope/__init__.py', 'src/zope/foo', 'src/zope/foo/__init__.py', 'src/zope/foo/language.po', 'src/zope/foo/language.mo', 'src/zope.foo.egg-info', 'src/zope.foo.egg-info/SOURCES.txt', ]) expected = canonical_file_list([ 'setup.py', 'README.txt', 'src', 'src/zope', 'src/zope/__init__.py', 'src/zope/foo', 'src/zope/foo/__init__.py', 'src/zope/foo/language.po', ]) ignore = IgnoreList.default() self.assertEqual(strip_sdist_extras(ignore, filelist), expected) def test_strip_sdist_extras_with_manifest(self): from check_manifest import ( IgnoreList, _get_ignore_from_manifest_lines, canonical_file_list, strip_sdist_extras, ) manifest_in = textwrap.dedent(""" graft src exclude *.cfg global-exclude *.mo prune src/dump recursive-exclude src/zope *.sh """) filelist = canonical_file_list([ '.github/ISSUE_TEMPLATE/bug_report.md', '.gitignore', 'setup.py', 'setup.cfg', 'MANIFEST.in', 'README.txt', 'src', 'src/helper.sh', 'src/dump', 'src/dump/__init__.py', 'src/zope', 'src/zope/__init__.py', 'src/zope/zopehelper.sh', 'src/zope/foo', 'src/zope/foo/__init__.py', 'src/zope/foo/language.po', 'src/zope/foo/language.mo', 'src/zope/foo/config.cfg', 'src/zope/foo/foohelper.sh', 'src/zope.foo.egg-info', 'src/zope.foo.egg-info/SOURCES.txt', ]) expected = canonical_file_list([ 'setup.py', 'MANIFEST.in', 'README.txt', 'src', 'src/helper.sh', 'src/zope', 'src/zope/__init__.py', 'src/zope/foo', 'src/zope/foo/__init__.py', 'src/zope/foo/language.po', 'src/zope/foo/config.cfg', ]) ignore = IgnoreList.default() ignore += _get_ignore_from_manifest_lines(manifest_in.splitlines(), self.ui) result = strip_sdist_extras(ignore, filelist) self.assertEqual(result, expected) def test_find_bad_ideas(self): from check_manifest import find_bad_ideas filelist = [ '.gitignore', 'setup.py', 'setup.cfg', 'README.txt', 'src', 'src/zope', 'src/zope/__init__.py', 'src/zope/foo', 'src/zope/foo/__init__.py', 'src/zope/foo/language.po', 'src/zope/foo/language.mo', 'src/zope.foo.egg-info', 'src/zope.foo.egg-info/SOURCES.txt', ] expected = [ 'src/zope/foo/language.mo', 'src/zope.foo.egg-info', ] self.assertEqual(find_bad_ideas(filelist), expected) def test_find_suggestions(self): from check_manifest import find_suggestions self.assertEqual(find_suggestions(['buildout.cfg']), (['include buildout.cfg'], [])) self.assertEqual(find_suggestions(['unknown.file~']), ([], ['unknown.file~'])) self.assertEqual(find_suggestions(['README.txt', 'CHANGES.txt']), (['include *.txt'], [])) filelist = [ 'docs/index.rst', 'docs/image.png', 'docs/Makefile', 'docs/unknown-file', 'src/etc/blah/blah/Makefile', ] expected_rules = [ 'recursive-include docs *.png', 'recursive-include docs *.rst', 'recursive-include docs Makefile', 'recursive-include src Makefile', ] expected_unknowns = ['docs/unknown-file'] self.assertEqual(find_suggestions(filelist), (expected_rules, expected_unknowns)) def test_find_suggestions_generic_fallback_rules(self): from check_manifest import find_suggestions self.assertEqual(find_suggestions(['Changelog']), (['include Changelog'], [])) self.assertEqual(find_suggestions(['id-lang.map']), (['include *.map'], [])) self.assertEqual(find_suggestions(['src/id-lang.map']), (['recursive-include src *.map'], [])) def test_is_package(self): from check_manifest import is_package j = os.path.join exists = {j('a', 'setup.py'), j('c', 'pyproject.toml')} with mock.patch('os.path.exists', lambda fn: fn in exists): self.assertTrue(is_package('a')) self.assertFalse(is_package('b')) self.assertTrue(is_package('c')) def test_extract_version_from_filename(self): from check_manifest import extract_version_from_filename as e self.assertEqual(e('dist/foo_bar-1.2.3.dev4+g12345.zip'), '1.2.3.dev4+g12345') self.assertEqual(e('dist/foo_bar-1.2.3.dev4+g12345.tar.gz'), '1.2.3.dev4+g12345') def test_get_ignore_from_manifest_lines(self): from check_manifest import IgnoreList, _get_ignore_from_manifest_lines parse = partial(_get_ignore_from_manifest_lines, ui=self.ui) self.assertEqual(parse([]), IgnoreList()) self.assertEqual(parse(['', ' ']), IgnoreList()) self.assertEqual(parse(['exclude *.cfg']), IgnoreList().exclude('*.cfg')) self.assertEqual(parse(['exclude *.cfg']), IgnoreList().exclude('*.cfg')) self.assertEqual(parse(['\texclude\t*.cfg foo.* bar.txt']), IgnoreList().exclude('*.cfg', 'foo.*', 'bar.txt')) self.assertEqual(parse(['exclude some/directory/*.cfg']), IgnoreList().exclude('some/directory/*.cfg')) self.assertEqual(parse(['include *.cfg']), IgnoreList()) self.assertEqual(parse(['global-exclude *.pyc']), IgnoreList().global_exclude('*.pyc')) self.assertEqual(parse(['global-exclude *.pyc *.sh']), IgnoreList().global_exclude('*.pyc', '*.sh')) self.assertEqual(parse(['recursive-exclude dir *.pyc']), IgnoreList().recursive_exclude('dir', '*.pyc')) self.assertEqual(parse(['recursive-exclude dir *.pyc foo*.sh']), IgnoreList().recursive_exclude('dir', '*.pyc', 'foo*.sh')) self.assertEqual(parse(['recursive-exclude dir nopattern.xml']), IgnoreList().recursive_exclude('dir', 'nopattern.xml')) # We should not fail when a recursive-exclude line is wrong: self.assertEqual(parse(['recursive-exclude dirwithoutpattern']), IgnoreList()) self.assertEqual(parse(['prune dir']), IgnoreList().prune('dir')) # And a mongo test case of everything at the end text = textwrap.dedent(""" exclude *.02 exclude *.03 04.* bar.txt exclude *.05 exclude some/directory/*.cfg global-exclude *.10 *.11 global-exclude *.12 include *.20 prune 30 recursive-exclude 40 *.41 recursive-exclude 42 *.43 44.* """).splitlines() self.assertEqual( parse(text), IgnoreList() .exclude('*.02', '*.03', '04.*', 'bar.txt', '*.05', 'some/directory/*.cfg') .global_exclude('*.10', '*.11', '*.12') .prune('30') .recursive_exclude('40', '*.41') .recursive_exclude('42', '*.43', '44.*') ) def test_get_ignore_from_manifest_lines_warns(self): from check_manifest import IgnoreList, _get_ignore_from_manifest_lines parse = partial(_get_ignore_from_manifest_lines, ui=self.ui) text = textwrap.dedent(""" graft a/ recursive-include /b *.txt """).splitlines() self.assertEqual(parse(text), IgnoreList()) self.assertEqual(self.ui.warnings, [ 'ERROR: Trailing slashes are not allowed in MANIFEST.in on Windows: a/', 'ERROR: Leading slashes are not allowed in MANIFEST.in on Windows: /b', ]) def test_get_ignore_from_manifest(self): from check_manifest import IgnoreList, _get_ignore_from_manifest filename = os.path.join(self.make_temp_dir(), 'MANIFEST.in') self.create_file(filename, textwrap.dedent(''' exclude \\ # yes, this is allowed! test.dat # https://github.com/mgedmin/check-manifest/issues/66 # docs/ folder ''')) ui = MockUI() self.assertEqual(_get_ignore_from_manifest(filename, ui), IgnoreList().exclude('test.dat')) self.assertEqual(ui.warnings, []) def test_get_ignore_from_manifest_warnings(self): from check_manifest import IgnoreList, _get_ignore_from_manifest filename = os.path.join(self.make_temp_dir(), 'MANIFEST.in') self.create_file(filename, textwrap.dedent(''' # this is bad: a file should not end with a backslash exclude test.dat \\ ''')) ui = MockUI() self.assertEqual(_get_ignore_from_manifest(filename, ui), IgnoreList().exclude('test.dat')) self.assertEqual(ui.warnings, [ "%s, line 2: continuation line immediately precedes end-of-file" % filename, ]) def test_should_use_pep517_no_pyproject_toml(self): from check_manifest import cd, should_use_pep_517 src_dir = self.make_temp_dir() with cd(src_dir): self.assertFalse(should_use_pep_517()) def test_should_use_pep517_no_build_system(self): from check_manifest import cd, should_use_pep_517 src_dir = self.make_temp_dir() filename = os.path.join(src_dir, 'pyproject.toml') self.create_file(filename, textwrap.dedent(''' [tool.check-manifest] ''')) with cd(src_dir): self.assertFalse(should_use_pep_517()) def test_should_use_pep517_no_build_backend(self): from check_manifest import cd, should_use_pep_517 src_dir = self.make_temp_dir() filename = os.path.join(src_dir, 'pyproject.toml') self.create_file(filename, textwrap.dedent(''' [build-system] requires = [ "setuptools >= 40.6.0", "wheel", ] ''')) with cd(src_dir): self.assertFalse(should_use_pep_517()) def test_should_use_pep517_yes_please(self): from check_manifest import cd, should_use_pep_517 src_dir = self.make_temp_dir() filename = os.path.join(src_dir, 'pyproject.toml') self.create_file(filename, textwrap.dedent(''' [build-system] requires = [ "setuptools >= 40.6.0", "wheel", ] build-backend = "setuptools.build_meta" ''')) with cd(src_dir): self.assertTrue(should_use_pep_517()) def _test_build_sdist_pep517(self, build_isolation): from check_manifest import build_sdist, cd, get_one_file_in src_dir = self.make_temp_dir() filename = os.path.join(src_dir, 'pyproject.toml') self.create_file(filename, textwrap.dedent(''' [build-system] requires = [ "setuptools >= 40.6.0", "wheel", ] build-backend = "setuptools.build_meta" ''')) out_dir = self.make_temp_dir() python = os.path.abspath(sys.executable) with cd(src_dir): build_sdist(out_dir, python=python, build_isolation=build_isolation) self.assertTrue(get_one_file_in(out_dir)) def test_build_sdist_pep517_isolated(self): self._test_build_sdist_pep517(build_isolation=True) def test_build_sdist_pep517_no_isolation(self): self._test_build_sdist_pep517(build_isolation=False) class TestConfiguration(unittest.TestCase): def setUp(self): self.oldpwd = os.getcwd() self.tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest') os.chdir(self.tmpdir) self.ui = MockUI() def tearDown(self): os.chdir(self.oldpwd) rmtree(self.tmpdir) def test_read_config_no_config(self): import check_manifest ignore, ignore_bad_ideas = check_manifest.read_config() self.assertEqual(ignore, check_manifest.IgnoreList.default()) def test_read_setup_config_no_section(self): import check_manifest with open('setup.cfg', 'w') as f: f.write('[pep8]\nignore =\n') ignore, ignore_bad_ideas = check_manifest.read_config() self.assertEqual(ignore, check_manifest.IgnoreList.default()) def test_read_pyproject_config_no_section(self): import check_manifest with open('pyproject.toml', 'w') as f: f.write('[tool.pep8]\nignore = []\n') ignore, ignore_bad_ideas = check_manifest.read_config() self.assertEqual(ignore, check_manifest.IgnoreList.default()) def test_read_setup_config_no_option(self): import check_manifest with open('setup.cfg', 'w') as f: f.write('[check-manifest]\n') ignore, ignore_bad_ideas = check_manifest.read_config() self.assertEqual(ignore, check_manifest.IgnoreList.default()) def test_read_pyproject_config_no_option(self): import check_manifest with open('pyproject.toml', 'w') as f: f.write('[tool.check-manifest]\n') ignore, ignore_bad_ideas = check_manifest.read_config() self.assertEqual(ignore, check_manifest.IgnoreList.default()) def test_read_setup_config_extra_ignores(self): import check_manifest with open('setup.cfg', 'w') as f: f.write('[check-manifest]\nignore = foo\n bar*\n') ignore, ignore_bad_ideas = check_manifest.read_config() expected = check_manifest.IgnoreList.default().global_exclude('foo', 'bar*') self.assertEqual(ignore, expected) def test_read_pyproject_config_extra_ignores(self): import check_manifest with open('pyproject.toml', 'w') as f: f.write('[tool.check-manifest]\nignore = ["foo", "bar*"]\n') ignore, ignore_bad_ideas = check_manifest.read_config() expected = check_manifest.IgnoreList.default().global_exclude('foo', 'bar*') self.assertEqual(ignore, expected) def test_read_setup_config_override_ignores(self): import check_manifest with open('setup.cfg', 'w') as f: f.write('[check-manifest]\nignore = foo\n\n bar\n') f.write('ignore-default-rules = yes\n') ignore, ignore_bad_ideas = check_manifest.read_config() expected = check_manifest.IgnoreList().global_exclude('foo', 'bar') self.assertEqual(ignore, expected) def test_read_pyproject_config_override_ignores(self): import check_manifest with open('pyproject.toml', 'w') as f: f.write('[tool.check-manifest]\nignore = ["foo", "bar"]\n') f.write('ignore-default-rules = true\n') ignore, ignore_bad_ideas = check_manifest.read_config() expected = check_manifest.IgnoreList().global_exclude('foo', 'bar') self.assertEqual(ignore, expected) def test_read_setup_config_ignore_bad_ideas(self): import check_manifest with open('setup.cfg', 'w') as f: f.write('[check-manifest]\n' 'ignore-bad-ideas = \n' ' foo\n' ' bar*\n') ignore, ignore_bad_ideas = check_manifest.read_config() expected = check_manifest.IgnoreList().global_exclude('foo', 'bar*') self.assertEqual(ignore_bad_ideas, expected) def test_read_pyproject_config_ignore_bad_ideas(self): import check_manifest with open('pyproject.toml', 'w') as f: f.write('[tool.check-manifest]\n' 'ignore-bad-ideas = ["foo", "bar*"]\n') ignore, ignore_bad_ideas = check_manifest.read_config() expected = check_manifest.IgnoreList().global_exclude('foo', 'bar*') self.assertEqual(ignore_bad_ideas, expected) def test_read_manifest_no_manifest(self): import check_manifest ignore = check_manifest.read_manifest(self.ui) self.assertEqual(ignore, check_manifest.IgnoreList()) def test_read_manifest(self): import check_manifest from check_manifest import IgnoreList with open('MANIFEST.in', 'w') as f: f.write('exclude *.gif\n') f.write('global-exclude *.png\n') ignore = check_manifest.read_manifest(self.ui) self.assertEqual(ignore, IgnoreList().exclude('*.gif').global_exclude('*.png')) class TestMain(unittest.TestCase): def setUp(self): self._cm_patcher = mock.patch('check_manifest.check_manifest') self._check_manifest = self._cm_patcher.start() self._se_patcher = mock.patch('sys.exit') self._sys_exit = self._se_patcher.start() self.ui = MockUI() self._ui_patcher = mock.patch('check_manifest.UI', self._make_ui) self._ui_patcher.start() self._orig_sys_argv = sys.argv sys.argv = ['check-manifest'] def tearDown(self): sys.argv = self._orig_sys_argv self._se_patcher.stop() self._cm_patcher.stop() self._ui_patcher.stop() def _make_ui(self, verbosity): self.ui.verbosity = verbosity return self.ui def test(self): from check_manifest import main sys.argv.append('-v') main() def test_exit_code_1_on_error(self): from check_manifest import main self._check_manifest.return_value = False main() self._sys_exit.assert_called_with(1) def test_exit_code_2_on_failure(self): from check_manifest import Failure, main self._check_manifest.side_effect = Failure('msg') main() self.assertEqual(self.ui.errors, ['msg']) self._sys_exit.assert_called_with(2) def test_extra_ignore_args(self): import check_manifest sys.argv.append('--ignore=x,y,z*') check_manifest.main() ignore = check_manifest.IgnoreList().global_exclude('x', 'y', 'z*') self.assertEqual(self._check_manifest.call_args.kwargs['extra_ignore'], ignore) def test_ignore_bad_ideas_args(self): import check_manifest sys.argv.append('--ignore-bad-ideas=x,y,z*') check_manifest.main() ignore = check_manifest.IgnoreList().global_exclude('x', 'y', 'z*') self.assertEqual(self._check_manifest.call_args.kwargs['extra_ignore_bad_ideas'], ignore) def test_verbose_arg(self): import check_manifest sys.argv.append('--verbose') check_manifest.main() self.assertEqual(self.ui.verbosity, 2) def test_quiet_arg(self): import check_manifest sys.argv.append('--quiet') check_manifest.main() self.assertEqual(self.ui.verbosity, 0) def test_verbose_and_quiet_arg(self): import check_manifest sys.argv.append('--verbose') sys.argv.append('--quiet') check_manifest.main() # the two arguments cancel each other out: # 1 (default verbosity) + 1 - 1 = 1. self.assertEqual(self.ui.verbosity, 1) class TestZestIntegration(unittest.TestCase): def setUp(self): sys.modules['zest'] = mock.Mock() sys.modules['zest.releaser'] = mock.Mock() sys.modules['zest.releaser.utils'] = mock.Mock() self.ask = sys.modules['zest.releaser.utils'].ask self.ui = MockUI() self._ui_patcher = mock.patch('check_manifest.UI', return_value=self.ui) self._ui_patcher.start() def tearDown(self): self._ui_patcher.stop() del sys.modules['zest.releaser.utils'] del sys.modules['zest.releaser'] del sys.modules['zest'] @mock.patch('check_manifest.is_package', lambda d: False) @mock.patch('check_manifest.check_manifest') def test_zest_releaser_check_not_a_package(self, check_manifest): from check_manifest import zest_releaser_check zest_releaser_check(dict(workingdir='.')) check_manifest.assert_not_called() @mock.patch('check_manifest.is_package', lambda d: True) @mock.patch('check_manifest.check_manifest') def test_zest_releaser_check_user_disagrees(self, check_manifest): from check_manifest import zest_releaser_check self.ask.return_value = False zest_releaser_check(dict(workingdir='.')) check_manifest.assert_not_called() @mock.patch('check_manifest.is_package', lambda d: True) @mock.patch('sys.exit') @mock.patch('check_manifest.check_manifest') def test_zest_releaser_check_all_okay(self, check_manifest, sys_exit): from check_manifest import zest_releaser_check self.ask.return_value = True check_manifest.return_value = True zest_releaser_check(dict(workingdir='.')) sys_exit.assert_not_called() @mock.patch('check_manifest.is_package', lambda d: True) @mock.patch('sys.exit') @mock.patch('check_manifest.check_manifest') def test_zest_releaser_check_error_user_aborts(self, check_manifest, sys_exit): from check_manifest import zest_releaser_check self.ask.side_effect = [True, False] check_manifest.return_value = False zest_releaser_check(dict(workingdir='.')) sys_exit.assert_called_with(1) @mock.patch('check_manifest.is_package', lambda d: True) @mock.patch('sys.exit') @mock.patch('check_manifest.check_manifest') def test_zest_releaser_check_error_user_plods_on(self, check_manifest, sys_exit): from check_manifest import zest_releaser_check self.ask.side_effect = [True, True] check_manifest.return_value = False zest_releaser_check(dict(workingdir='.')) sys_exit.assert_not_called() @mock.patch('check_manifest.is_package', lambda d: True) @mock.patch('sys.exit') @mock.patch('check_manifest.check_manifest') def test_zest_releaser_check_failure_user_aborts(self, check_manifest, sys_exit): from check_manifest import Failure, zest_releaser_check self.ask.side_effect = [True, False] check_manifest.side_effect = Failure('msg') zest_releaser_check(dict(workingdir='.')) self.assertEqual(self.ui.errors, ['msg']) sys_exit.assert_called_with(2) @mock.patch('check_manifest.is_package', lambda d: True) @mock.patch('sys.exit') @mock.patch('check_manifest.check_manifest') def test_zest_releaser_check_failure_user_plods_on(self, check_manifest, sys_exit): from check_manifest import Failure, zest_releaser_check self.ask.side_effect = [True, True] check_manifest.side_effect = Failure('msg') zest_releaser_check(dict(workingdir='.')) self.assertEqual(self.ui.errors, ['msg']) sys_exit.assert_not_called() class VCSHelper: # override in subclasses command = None # type: Optional[str] def is_installed(self): try: p = subprocess.Popen([self.command, '--version'], stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = p.communicate() rc = p.wait() return (rc == 0) except OSError: return False def _run(self, *command): # Windows doesn't like Unicode arguments to subprocess.Popen(), on Py2: # https://github.com/mgedmin/check-manifest/issues/23#issuecomment-33933031 if str is bytes: command = [s.encode(locale.getpreferredencoding()) for s in command] print('$', ' '.join(command)) p = subprocess.Popen(command, stdout=subprocess.PIPE, stderr=subprocess.STDOUT) stdout, stderr = p.communicate() rc = p.wait() if stdout: print( stdout if isinstance(stdout, str) else stdout.decode('ascii', 'backslashreplace') ) if rc: raise subprocess.CalledProcessError(rc, command[0], output=stdout) class VCSMixin: def setUp(self): if not self.vcs.is_installed() and CAN_SKIP_TESTS: self.skipTest("%s is not installed" % self.vcs.command) self.tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest') self.olddir = os.getcwd() os.chdir(self.tmpdir) self.ui = MockUI() def tearDown(self): os.chdir(self.olddir) rmtree(self.tmpdir) def _create_file(self, filename): assert not os.path.isabs(filename) basedir = os.path.dirname(filename) if basedir and not os.path.isdir(basedir): os.makedirs(basedir) open(filename, 'w').close() def _create_files(self, filenames): for filename in filenames: self._create_file(filename) def _init_vcs(self): self.vcs._init_vcs() def _add_to_vcs(self, filenames): self.vcs._add_to_vcs(filenames) def _commit(self): self.vcs._commit() def _create_and_add_to_vcs(self, filenames): self._create_files(filenames) self._add_to_vcs(filenames) def test_get_vcs_files(self): from check_manifest import get_vcs_files self._init_vcs() self._create_and_add_to_vcs(['a.txt', 'b/b.txt', 'b/c/d.txt']) self._commit() self._create_files(['b/x.txt', 'd/d.txt', 'i.txt']) self.assertEqual(get_vcs_files(self.ui), ['a.txt', 'b/b.txt', 'b/c/d.txt']) def test_get_vcs_files_added_but_uncommitted(self): from check_manifest import get_vcs_files self._init_vcs() self._create_and_add_to_vcs(['a.txt', 'b/b.txt', 'b/c/d.txt']) self._create_files(['b/x.txt', 'd/d.txt', 'i.txt']) self.assertEqual(get_vcs_files(self.ui), ['a.txt', 'b/b.txt', 'b/c/d.txt']) def test_get_vcs_files_deleted_but_not_removed(self): if self.vcs.command == 'bzr': self.skipTest("this cosmetic feature is not supported with bzr") # see the longer explanation in test_missing_source_files from check_manifest import get_vcs_files self._init_vcs() self._create_and_add_to_vcs(['a.txt']) self._commit() os.unlink('a.txt') self.assertEqual(get_vcs_files(self.ui), ['a.txt']) def test_get_vcs_files_in_a_subdir(self): from check_manifest import get_vcs_files self._init_vcs() self._create_and_add_to_vcs(['a.txt', 'b/b.txt', 'b/c/d.txt']) self._commit() self._create_files(['b/x.txt', 'd/d.txt', 'i.txt']) os.chdir('b') self.assertEqual(get_vcs_files(self.ui), ['b.txt', 'c/d.txt']) def test_get_vcs_files_nonascii_filenames(self): # This test will fail if your locale is incapable of expressing # "eacute". UTF-8 or Latin-1 should work. from check_manifest import get_vcs_files self._init_vcs() filename = "\u00E9.txt" self._create_and_add_to_vcs([filename]) self.assertEqual(get_vcs_files(self.ui), [filename]) def test_get_vcs_files_empty(self): from check_manifest import get_vcs_files self._init_vcs() self.assertEqual(get_vcs_files(self.ui), []) class GitHelper(VCSHelper): command = 'git' def _init_vcs(self): self._run('git', 'init') self._run('git', 'config', 'user.name', 'Unit Test') self._run('git', 'config', 'user.email', 'test@example.com') def _add_to_vcs(self, filenames): # Note that we use --force to prevent errors when we want to # add foo.egg-info and the user running the tests has # '*.egg-info' in her global .gitignore file. self._run('git', 'add', '--force', '--', *filenames) def _commit(self): self._run('git', 'commit', '-m', 'Initial') class TestGit(VCSMixin, unittest.TestCase): vcs = GitHelper() def _init_repo_with_files(self, dirname, filenames): os.mkdir(dirname) os.chdir(dirname) self._init_vcs() self._create_and_add_to_vcs(filenames) self._commit() os.chdir(self.tmpdir) def _add_submodule(self, repo, subdir, subrepo): os.chdir(repo) self.vcs._run('git', 'submodule', 'add', subrepo, subdir) self._commit() os.chdir(self.tmpdir) def test_detect_git_submodule(self): from check_manifest import Failure, detect_vcs with self.assertRaises(Failure) as cm: detect_vcs(self.ui) self.assertEqual(str(cm.exception), "Couldn't find version control data" " (git/hg/bzr/svn supported)") # now create a .git file like in a submodule open(os.path.join(self.tmpdir, '.git'), 'w').close() self.assertEqual(detect_vcs(self.ui).metadata_name, '.git') def test_get_versioned_files_with_git_submodules(self): from check_manifest import get_vcs_files self._init_repo_with_files('repo1', ['file1', 'file2']) self._init_repo_with_files('repo2', ['file3']) self._init_repo_with_files('repo3', ['file4']) self._add_submodule('repo2', 'sub3', '../repo3') self._init_repo_with_files('main', ['file5', 'subdir/file6']) self._add_submodule('main', 'sub1', '../repo1') self._add_submodule('main', 'subdir/sub2', '../repo2') os.chdir('main') self.vcs._run('git', 'submodule', 'update', '--init', '--recursive') self.assertEqual( get_vcs_files(self.ui), [ '.gitmodules', 'file5', 'sub1/file1', 'sub1/file2', 'subdir/file6', 'subdir/sub2/.gitmodules', 'subdir/sub2/file3', 'subdir/sub2/sub3/file4', ]) def test_get_versioned_files_with_git_submodules_with_git_index_file_set(self): with mock.patch.dict(os.environ, {"GIT_INDEX_FILE": ".git/index"}): self.test_get_versioned_files_with_git_submodules() class BzrHelper(VCSHelper): command = 'bzr' def _init_vcs(self): self._run('bzr', 'init') self._run('bzr', 'whoami', '--branch', 'Unit Test ') def _add_to_vcs(self, filenames): self._run('bzr', 'add', '--', *filenames) def _commit(self): self._run('bzr', 'commit', '-m', 'Initial') class TestBzr(VCSMixin, unittest.TestCase): vcs = BzrHelper() @unittest.skipIf(HAS_OEM_CODEC, "Python 3.6 lets us use 'oem' codec instead of guessing") class TestBzrTerminalCharsetDetectionOnOldPythons(unittest.TestCase): @mock.patch('sys.stdin') @mock.patch('sys.stdout') def test_terminal_encoding_not_known(self, mock_stdout, mock_stdin): from check_manifest import Bazaar mock_stdout.encoding = None mock_stdin.encoding = None self.assertEqual(Bazaar._get_terminal_encoding(), None) @mock.patch('sys.stdout') def test_terminal_encoding_stdout_known(self, mock_stdout): from check_manifest import Bazaar mock_stdout.encoding = 'UTF-8' self.assertEqual(Bazaar._get_terminal_encoding(), 'UTF-8') @mock.patch('sys.stdin') @mock.patch('sys.stdout') def test_terminal_encoding_stdin_known(self, mock_stdout, mock_stdin): from check_manifest import Bazaar mock_stdout.encoding = None mock_stdin.encoding = 'UTF-8' self.assertEqual(Bazaar._get_terminal_encoding(), 'UTF-8') @mock.patch('sys.stdout') def test_terminal_encoding_cp0(self, mock_stdout): from check_manifest import Bazaar mock_stdout.encoding = 'cp0' self.assertEqual(Bazaar._get_terminal_encoding(), None) @unittest.skipIf(not HAS_OEM_CODEC, "'oem' codec not available on Python before 3.6") class TestBzrTerminalCharsetDetectionOnNewPythons(unittest.TestCase): def test_terminal_encoding_cp0(self): from check_manifest import Bazaar self.assertEqual(Bazaar._get_terminal_encoding(), "oem") class HgHelper(VCSHelper): command = 'hg' def _init_vcs(self): self._run('hg', 'init') with open('.hg/hgrc', 'a') as f: f.write('\n[ui]\nusername = Unit Test ') self.assertFalse(svn.is_interesting(entry)) self.assertEqual( ui.warnings, ['svn status --xml parse error:' ' without ']) class TestUserInterface(unittest.TestCase): def make_ui(self, verbosity=1): from check_manifest import UI ui = UI(verbosity=verbosity) ui.stdout = StringIO() ui.stderr = StringIO() return ui def test_info(self): ui = self.make_ui(verbosity=1) ui.info("Reticulating splines") self.assertEqual(ui.stdout.getvalue(), "Reticulating splines\n") def test_info_verbose(self): ui = self.make_ui(verbosity=2) ui.info("Reticulating splines") self.assertEqual(ui.stdout.getvalue(), "Reticulating splines\n") def test_info_quiet(self): ui = self.make_ui(verbosity=0) ui.info("Reticulating splines") self.assertEqual(ui.stdout.getvalue(), "") def test_info_begin_continue_end(self): ui = self.make_ui(verbosity=1) ui.info_begin("Reticulating splines...") ui.info_continue(" nearly done...") ui.info_continue(" almost done...") ui.info_end(" done!") self.assertEqual(ui.stdout.getvalue(), "") def test_info_begin_continue_end_verbose(self): ui = self.make_ui(verbosity=2) ui.info_begin("Reticulating splines...") ui.info_continue(" nearly done...") ui.info_continue(" almost done...") ui.info_end(" done!") self.assertEqual( ui.stdout.getvalue(), "Reticulating splines... nearly done... almost done... done!\n") def test_info_emits_newline_when_needed(self): ui = self.make_ui(verbosity=1) ui.info_begin("Computering...") ui.info("Forgot to turn the gas off!") self.assertEqual( ui.stdout.getvalue(), "Forgot to turn the gas off!\n") def test_info_emits_newline_when_needed_verbose(self): ui = self.make_ui(verbosity=2) ui.info_begin("Computering...") ui.info("Forgot to turn the gas off!") self.assertEqual( ui.stdout.getvalue(), "Computering...\n" "Forgot to turn the gas off!\n") def test_warning(self): ui = self.make_ui(verbosity=1) ui.info_begin("Computering...") ui.warning("Forgot to turn the gas off!") self.assertEqual(ui.stdout.getvalue(), "") self.assertEqual( ui.stderr.getvalue(), "Forgot to turn the gas off!\n") def test_warning_verbose(self): ui = self.make_ui(verbosity=2) ui.info_begin("Computering...") ui.warning("Forgot to turn the gas off!") self.assertEqual( ui.stdout.getvalue(), "Computering...\n") self.assertEqual( ui.stderr.getvalue(), "Forgot to turn the gas off!\n") def test_error(self): ui = self.make_ui(verbosity=1) ui.info_begin("Computering...") ui.error("Forgot to turn the gas off!") self.assertEqual(ui.stdout.getvalue(), "") self.assertEqual( ui.stderr.getvalue(), "Forgot to turn the gas off!\n") def test_error_verbose(self): ui = self.make_ui(verbosity=2) ui.info_begin("Computering...") ui.error("Forgot to turn the gas off!") self.assertEqual( ui.stdout.getvalue(), "Computering...\n") self.assertEqual( ui.stderr.getvalue(), "Forgot to turn the gas off!\n") class TestIgnoreList(unittest.TestCase): def setUp(self): from check_manifest import IgnoreList self.ignore = IgnoreList() def test_repr(self): from check_manifest import IgnoreList ignore = IgnoreList() self.assertEqual(repr(ignore), "IgnoreList([])") def test_exclude_pattern(self): self.ignore.exclude('*.txt') self.assertEqual(self.ignore.filter([ 'foo.md', 'bar.txt', 'subdir/bar.txt', ]), [ 'foo.md', 'subdir/bar.txt', ]) def test_exclude_file(self): self.ignore.exclude('bar.txt') self.assertEqual(self.ignore.filter([ 'foo.md', 'bar.txt', 'subdir/bar.txt', ]), [ 'foo.md', 'subdir/bar.txt', ]) def test_exclude_doest_apply_to_directories(self): self.ignore.exclude('subdir') self.assertEqual(self.ignore.filter([ 'foo.md', 'subdir/bar.txt', ]), [ 'foo.md', 'subdir/bar.txt', ]) def test_global_exclude(self): self.ignore.global_exclude('a*.txt') self.assertEqual(self.ignore.filter([ 'bar.txt', # make sure full filenames are matched 'afile.txt', 'subdir/afile.txt', 'adir/file.txt', # make sure * doesn't match / ]), [ 'bar.txt', 'adir/file.txt', ]) def test_global_exclude_does_not_apply_to_directories(self): self.ignore.global_exclude('subdir') self.assertEqual(self.ignore.filter([ 'bar.txt', 'subdir/afile.txt', ]), [ 'bar.txt', 'subdir/afile.txt', ]) def test_recursive_exclude(self): self.ignore.recursive_exclude('subdir', 'a*.txt') self.assertEqual(self.ignore.filter([ 'afile.txt', 'subdir/afile.txt', 'subdir/extra/afile.txt', 'subdir/adir/file.txt', 'other/afile.txt', ]), [ 'afile.txt', 'subdir/adir/file.txt', 'other/afile.txt', ]) def test_recursive_exclude_does_not_apply_to_directories(self): self.ignore.recursive_exclude('subdir', 'dir') self.assertEqual(self.ignore.filter([ 'afile.txt', 'subdir/dir/afile.txt', ]), [ 'afile.txt', 'subdir/dir/afile.txt', ]) def test_recursive_exclude_can_prune(self): self.ignore.recursive_exclude('subdir', '*') self.assertEqual(self.ignore.filter([ 'afile.txt', 'subdir/afile.txt', 'subdir/dir/afile.txt', 'subdir/dir/dir/afile.txt', ]), [ 'afile.txt', ]) def test_prune(self): self.ignore.prune('subdir') self.assertEqual(self.ignore.filter([ 'foo.md', 'subdir/bar.txt', 'unrelated/subdir/baz.txt', ]), [ 'foo.md', 'unrelated/subdir/baz.txt', ]) def test_prune_subdir(self): self.ignore.prune('a/b') self.assertEqual(self.ignore.filter([ 'foo.md', 'a/b/bar.txt', 'a/c/bar.txt', ]), [ 'foo.md', 'a/c/bar.txt', ]) def test_prune_glob(self): self.ignore.prune('su*r') self.assertEqual(self.ignore.filter([ 'foo.md', 'subdir/bar.txt', 'unrelated/subdir/baz.txt', ]), [ 'foo.md', 'unrelated/subdir/baz.txt', ]) def test_prune_glob_is_not_too_greedy(self): self.ignore.prune('su*r') self.assertEqual(self.ignore.filter([ 'foo.md', # super-unrelated/subdir matches su*r if you allow * to match /, # which fnmatch does! 'super-unrelated/subdir/qux.txt', ]), [ 'foo.md', 'super-unrelated/subdir/qux.txt', ]) def test_default_excludes_pkg_info(self): from check_manifest import IgnoreList ignore = IgnoreList.default() self.assertEqual(ignore.filter([ 'PKG-INFO', 'bar.txt', ]), [ 'bar.txt', ]) def test_default_excludes_egg_info(self): from check_manifest import IgnoreList ignore = IgnoreList.default() self.assertEqual(ignore.filter([ 'mypackage.egg-info/PKG-INFO', 'mypackage.egg-info/SOURCES.txt', 'mypackage.egg-info/requires.txt', 'bar.txt', ]), [ 'bar.txt', ]) def test_default_excludes_egg_info_in_a_subdirectory(self): from check_manifest import IgnoreList ignore = IgnoreList.default() self.assertEqual(ignore.filter([ 'src/mypackage.egg-info/PKG-INFO', 'src/mypackage.egg-info/SOURCES.txt', 'src/mypackage.egg-info/requires.txt', 'bar.txt', ]), [ 'bar.txt', ]) def pick_installed_vcs(): preferred_order = [GitHelper, HgHelper, BzrHelper, SvnHelper] force = os.getenv('FORCE_TEST_VCS') if force: for cls in preferred_order: if force == cls.command: return cls() raise ValueError('Unsupported FORCE_TEST_VCS=%s (supported: %s)' % (force, '/'.join(cls.command for cls in preferred_order))) for cls in preferred_order: vcs = cls() if vcs.is_installed(): return vcs return None class TestCheckManifest(unittest.TestCase): _vcs = pick_installed_vcs() def setUp(self): if self._vcs is None: self.fail('at least one version control system should be installed') self.oldpwd = os.getcwd() self.tmpdir = tempfile.mkdtemp(prefix='test-', suffix='-check-manifest') os.chdir(self.tmpdir) self._stdout_patcher = mock.patch('sys.stdout', StringIO()) self._stdout_patcher.start() self._stderr_patcher = mock.patch('sys.stderr', StringIO()) self._stderr_patcher.start() def tearDown(self): self._stderr_patcher.stop() self._stdout_patcher.stop() os.chdir(self.oldpwd) rmtree(self.tmpdir) def _create_repo_with_code(self, add_to_vcs=True): self._vcs._init_vcs() with open('setup.py', 'w') as f: f.write("from setuptools import setup\n") f.write("setup(name='sample', py_modules=['sample'])\n") with open('sample.py', 'w') as f: f.write("# wow. such code. so amaze\n") if add_to_vcs: self._vcs._add_to_vcs(['setup.py', 'sample.py']) def _create_repo_with_code_in_subdir(self): os.mkdir('subdir') os.chdir('subdir') self._create_repo_with_code() # NB: when self._vcs is SvnHelper, we're actually in # ./subdir/checkout rather than in ./subdir subdir = os.path.basename(os.getcwd()) os.chdir(os.pardir) return subdir def _add_to_vcs(self, filename, content=''): if os.path.sep in filename and not os.path.isdir(os.path.dirname(filename)): os.makedirs(os.path.dirname(filename)) with open(filename, 'w') as f: f.write(content) self._vcs._add_to_vcs([filename]) def test_not_python_project(self): from check_manifest import Failure, check_manifest with self.assertRaises(Failure) as cm: check_manifest() self.assertEqual( str(cm.exception), "This is not a Python project (no setup.py/pyproject.toml).") def test_forgot_to_git_add_anything(self): from check_manifest import Failure, check_manifest self._create_repo_with_code(add_to_vcs=False) with self.assertRaises(Failure) as cm: check_manifest() self.assertEqual(str(cm.exception), "There are no files added to version control!") def test_all_is_well(self): from check_manifest import check_manifest self._create_repo_with_code() self.assertTrue(check_manifest(), sys.stderr.getvalue()) def test_relative_pathname(self): from check_manifest import check_manifest subdir = self._create_repo_with_code_in_subdir() self.assertTrue(check_manifest(subdir), sys.stderr.getvalue()) def test_relative_python(self): # https://github.com/mgedmin/check-manifest/issues/36 from check_manifest import check_manifest subdir = self._create_repo_with_code_in_subdir() python = os.path.relpath(sys.executable) self.assertTrue(check_manifest(subdir, python=python), sys.stderr.getvalue()) def test_python_from_path(self): # https://github.com/mgedmin/check-manifest/issues/57 from check_manifest import check_manifest # We need a Python interpeter to be in PATH. python = 'python' if hasattr(shutil, 'which'): for python in 'python', 'python3', os.path.basename(sys.executable): if shutil.which(python): break subdir = self._create_repo_with_code_in_subdir() self.assertTrue(check_manifest(subdir, python=python), sys.stderr.getvalue()) def test_extra_ignore(self): from check_manifest import IgnoreList, check_manifest self._create_repo_with_code() self._add_to_vcs('unrelated.txt') ignore = IgnoreList().global_exclude('*.txt') self.assertTrue(check_manifest(extra_ignore=ignore), sys.stderr.getvalue()) def test_suggestions(self): from check_manifest import check_manifest self._create_repo_with_code() self._add_to_vcs('unrelated.txt') self.assertFalse(check_manifest()) self.assertIn("missing from sdist:\n unrelated.txt", sys.stderr.getvalue()) self.assertIn("suggested MANIFEST.in rules:\n include *.txt", sys.stdout.getvalue()) def test_suggestions_create(self): from check_manifest import check_manifest self._create_repo_with_code() self._add_to_vcs('unrelated.txt') self.assertFalse(check_manifest(create=True)) self.assertIn("missing from sdist:\n unrelated.txt", sys.stderr.getvalue()) self.assertIn("suggested MANIFEST.in rules:\n include *.txt", sys.stdout.getvalue()) self.assertIn("creating MANIFEST.in", sys.stdout.getvalue()) with open('MANIFEST.in') as f: self.assertEqual(f.read(), "include *.txt\n") def test_suggestions_update(self): from check_manifest import check_manifest self._create_repo_with_code() self._add_to_vcs('unrelated.txt') self._add_to_vcs('MANIFEST.in', '#tbd') self.assertFalse(check_manifest(update=True)) self.assertIn("missing from sdist:\n unrelated.txt", sys.stderr.getvalue()) self.assertIn("suggested MANIFEST.in rules:\n include *.txt", sys.stdout.getvalue()) self.assertIn("updating MANIFEST.in", sys.stdout.getvalue()) with open('MANIFEST.in') as f: self.assertEqual( f.read(), "#tbd\n# added by check-manifest\ninclude *.txt\n") def test_suggestions_all_unknown_patterns(self): from check_manifest import check_manifest self._create_repo_with_code() self._add_to_vcs('.dunno-what-to-do-with-this') self.assertFalse(check_manifest(update=True)) self.assertIn("missing from sdist:\n .dunno-what-to-do-with-this", sys.stderr.getvalue()) self.assertIn( "don't know how to come up with rules matching any of the files, sorry!", sys.stdout.getvalue()) def test_suggestions_some_unknown_patterns(self): from check_manifest import check_manifest self._create_repo_with_code() self._add_to_vcs('.dunno-what-to-do-with-this') self._add_to_vcs('unrelated.txt') self.assertFalse(check_manifest(update=True)) self.assertIn( "don't know how to come up with rules matching\n .dunno-what-to-do-with-this", sys.stdout.getvalue()) self.assertIn("creating MANIFEST.in", sys.stdout.getvalue()) with open('MANIFEST.in') as f: self.assertEqual(f.read(), "include *.txt\n") def test_MANIFEST_in_does_not_need_to_be_added_to_be_considered(self): from check_manifest import check_manifest self._create_repo_with_code() self._add_to_vcs('unrelated.txt') with open('MANIFEST.in', 'w') as f: f.write("include *.txt\n") self.assertFalse(check_manifest()) self.assertIn("missing from VCS:\n MANIFEST.in", sys.stderr.getvalue()) self.assertNotIn("missing from sdist", sys.stderr.getvalue()) def test_setup_py_does_not_need_to_be_added_to_be_considered(self): from check_manifest import check_manifest self._create_repo_with_code(add_to_vcs=False) self._add_to_vcs('sample.py') self.assertFalse(check_manifest()) self.assertIn("missing from VCS:\n setup.py", sys.stderr.getvalue()) self.assertNotIn("missing from sdist", sys.stderr.getvalue()) def test_bad_ideas(self): from check_manifest import check_manifest self._create_repo_with_code() self._add_to_vcs('foo.egg-info') self._add_to_vcs('moo.mo') self.assertFalse(check_manifest()) self.assertIn("you have foo.egg-info in source control!", sys.stderr.getvalue()) self.assertIn("this also applies to the following:\n moo.mo", sys.stderr.getvalue()) def test_ignore_bad_ideas(self): from check_manifest import IgnoreList, check_manifest self._create_repo_with_code() with open('setup.cfg', 'w') as f: f.write('[check-manifest]\n' 'ignore =\n' ' subdir/bar.egg-info\n' 'ignore-bad-ideas =\n' ' subdir/bar.egg-info\n') self._add_to_vcs('foo.egg-info') self._add_to_vcs('moo.mo') self._add_to_vcs(os.path.join('subdir', 'bar.egg-info')) ignore = IgnoreList().global_exclude('*.mo') self.assertFalse(check_manifest(extra_ignore_bad_ideas=ignore)) self.assertIn("you have foo.egg-info in source control!", sys.stderr.getvalue()) self.assertNotIn("moo.mo", sys.stderr.getvalue()) self.assertNotIn("bar.egg-info", sys.stderr.getvalue()) def test_missing_source_files(self): # https://github.com/mgedmin/check-manifest/issues/32 from check_manifest import check_manifest self._create_repo_with_code() self._add_to_vcs('missing.py') os.unlink('missing.py') check_manifest() if self._vcs.command != 'bzr': # 'bzr ls' doesn't list files that were deleted but not # marked for deletion. 'bzr st' does, but it doesn't list # unmodified files. Importing bzrlib and using the API to # get the file list we need is (a) complicated, (b) opens # the optional dependency can of worms, and (c) not viable # under Python 3 unless we fork off a Python 2 subprocess. # Manually combining 'bzr ls' and 'bzr st' outputs just to # produce a cosmetic warning message seems like overkill. self.assertIn( "some files listed as being under source control are missing:\n" " missing.py", sys.stderr.getvalue()) ././@PaxHeader0000000000000000000000000000002600000000000011453 xustar000000000000000022 mtime=1608651454.0 check-manifest-0.46/tox.ini0000664000175000017500000000163600000000000014700 0ustar00mgmg00000000000000[tox] envlist = py36,py37,py38,py39,pypy3,flake8 [testenv] passenv = LANG LC_CTYPE LC_ALL MSYSTEM extras = test commands = pytest {posargs} [testenv:coverage] deps = coverage commands = coverage run -m pytest coverage report -m --fail-under=100 [testenv:check-manifest] basepython = python3 skip_install = true deps = check-manifest commands = check-manifest {posargs} [testenv:check-python-versions] basepython = python3 skip_install = true deps = check-python-versions commands = check-python-versions {posargs} [testenv:flake8] basepython = python3 skip_install = true deps = flake8 commands = flake8 {posargs:check_manifest.py setup.py tests.py} [testenv:mypy] basepython = python3 skip_install = true deps = mypy commands = mypy {posargs:check_manifest.py} [testenv:isort] basepython = python3 skip_install = true deps = isort commands = isort {posargs: -c check_manifest.py setup.py tests.py}