From 6d63caaa3f37dd15b336a5ff533d5f74d2d85336 Mon Sep 17 00:00:00 2001 From: Jarry Shaw Date: Thu, 15 Oct 2026 20:00:06 -0400 Subject: [PATCH] chore: drop the MANIFEST.in lines that match nothing (#1794) - global-include *.rst: every tracked .rst is under docs/, which prune docs drops. Only with a conda _extern tree did it ship anything: two vendored files, aenum/doc/aenum.rst and html5lib's dist-info AUTHORS.rst, neither read at runtime. - include pcapkit/_extern: include matches files, not a directory. The conda recipe runs pip install . on the tree and builds no sdist; _extern's modules reach the wheel through package discovery. - exclude pcapkit/protocols/*/NotImplemented: the placeholders are directories; they ship either way, as documentation.rst records. - MANIFEST.in comments and documentation.rst stop citing *.rst. - ManifestTests: dropping any non-prune line must change what the template selects; on origin/main it fails on exactly these lines. sdist 1249 and wheel 534 entries, unchanged; both warnings gone. tests/project: 951 passed, 1 skipped. Closes #1794 --- MANIFEST.in | 15 +++--- .../conventions/documentation.rst | 4 +- tests/project/test_build_leftovers_1773.py | 54 ++++++++++++++++++- 3 files changed, 64 insertions(+), 9 deletions(-) diff --git a/MANIFEST.in b/MANIFEST.in index 88b9abc61..efb018c1b 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,8 +1,7 @@ global-exclude .DS_Store -global-include *.rst global-include *.py -# Neither global-include reaches Markdown, which the examples' READMEs are (#719). +# The global-include does not reach Markdown, which the examples' READMEs are (#719). recursive-include examples *.md prune .eggs @@ -53,7 +52,7 @@ include README.md include LICENSE # Load-bearing, and missed for the same reason as ``CHANGELOG.md`` below: -# neither ``global-include`` at the top of this file can reach a ``.cff``, so +# the ``global-include`` at the top of this file cannot reach a ``.cff``, so # without this line ``CITATION.cff`` sits in the repository and in no source # distribution at all -- measured by building one each way, not assumed. Nor is # there a default to fall back on: setuptools ships ``LICENSE`` from its @@ -71,7 +70,7 @@ include CITATION.cff # The changelog needs two lines of its own, and neither is redundant. # -# ``global-include *.rst`` above matches only ``*.rst``, so the root +# ``global-include *.py`` above matches only ``*.py``, so the root # ``CHANGELOG.md`` is picked up by nothing and would be absent from every source # distribution without this line -- measured, not assumed. include CHANGELOG.md @@ -85,5 +84,9 @@ include CHANGELOG.md # shipped, because it is a ``toctree`` that only Sphinx can read. recursive-include docs/source/changelog *.rst -include pcapkit/_extern -exclude pcapkit/protocols/*/NotImplemented +# Nothing here names ``pcapkit/_extern``. ``conda/pypcapkit/build.sh`` and +# ``bld.bat`` create it and then run ``pip install .``, which builds the wheel +# straight from the tree, so the conda recipe builds no sdist; its modules +# reach that wheel through package discovery. A line naming it would warn in +# every other build, and ``include`` of a directory matched nothing even in the +# conda one (#1794). diff --git a/docs/source/contributing/conventions/documentation.rst b/docs/source/contributing/conventions/documentation.rst index e77da36e7..9358084f0 100644 --- a/docs/source/contributing/conventions/documentation.rst +++ b/docs/source/contributing/conventions/documentation.rst @@ -298,8 +298,8 @@ Format and Mechanics ruled this on :issue:`719`, correcting a blanket *always* ``.rst`` that had been in circulation: the Sphinx documentation is reST, and the other documents -- the READMEs included -- are Markdown where that applies. ``CONTRIBUTING.md``'s own - *Documentation* section records the same split. Neither ``global-include`` in - :file:`MANIFEST.in` reaches Markdown, so the two READMEs under :file:`examples/` + *Documentation* section records the same split. The ``global-include`` in + :file:`MANIFEST.in` does not reach Markdown, so the READMEs under :file:`examples/` ship through ``recursive-include examples *.md``, and any other Markdown file ships only if setuptools or an ``include`` line adds it. * **Prose names the real defining module, not a re-export.** One strand of the diff --git a/tests/project/test_build_leftovers_1773.py b/tests/project/test_build_leftovers_1773.py index 425db7bc3..673554b46 100644 --- a/tests/project/test_build_leftovers_1773.py +++ b/tests/project/test_build_leftovers_1773.py @@ -6,7 +6,8 @@ a real wheel of a stub package with :file:`setup.py`'s own ``cmdclass``. * Each backport's failure message names the feature that tool back-ports. * ``make profile`` reaches a Makefile that exists, and every ``prune`` in - :file:`MANIFEST.in` names a directory the repository tracks or ignores. + :file:`MANIFEST.in` names a directory the repository tracks or ignores. Every + other line there changes what the template selects (#1794). * No reStructuredText is tracked outside ``docs/`` (the #719 ruling), the examples' Markdown READMEs still ship in the sdist, and nothing points at a README path that no longer exists. @@ -213,6 +214,57 @@ def test_every_prune_names_a_tracked_or_ignored_directory(self) -> None: self.assertEqual(ignored.returncode, 0, f'prune {directory} names a directory git neither tracks nor ignores') + def test_every_other_directive_changes_the_selection(self) -> None: + """Dropping any non-``prune`` line changes which tracked files the template selects. + + An exclusion may instead guard a path git ignores, as ``prune`` may above. + The template runs without setuptools' defaults, so ``include README.md`` + counts as effective here although a real build ships the README anyway + (#1794). + """ + files = tracked() + directives = [line.strip() for line in + (ROOT / 'MANIFEST.in').read_text(encoding='utf-8').splitlines() + if line.strip() and not line.lstrip().startswith('#')] + driver = textwrap.dedent(''' + import json, os, sys + from setuptools.command.egg_info import FileList + data = json.loads(sys.stdin.read()) + tracked = set(data['files']) + + def select(lines): + listing = FileList() + listing.allfiles = list(data['files']) + for line in lines: + listing.process_template_line(line) + return sorted({name.replace(os.sep, '/') for name in listing.files} & tracked) + + lines = data['lines'] + print(json.dumps([select(lines)] + [select(lines[:index] + lines[index + 1:]) + for index in range(len(lines))])) + ''') + result = subprocess.run([sys.executable, '-c', driver], cwd=ROOT, # nosec + input=json.dumps({'files': files, 'lines': directives}), + capture_output=True, text=True, check=True, timeout=120) + selected, *without = json.loads(result.stdout) + for line, rest in zip(directives, without): + action, *words = line.split() + if action == 'prune': + continue + with self.subTest(line=line): + if rest != selected: + continue + if action.endswith('exclude'): + if action.startswith('recursive-'): + targets = [f'{words[0]}/{pattern}' for pattern in words[1:]] + else: + targets = words + if all(subprocess.run(['git', 'check-ignore', '-q', '--no-index', # nosec + target], cwd=ROOT, check=False).returncode == 0 + for target in targets): + continue + self.fail(f'{line!r} changes nothing the template selects from the tracked files') + def test_no_restructuredtext_outside_docs(self) -> None: stray = [name for name in tracked() if name.endswith('.rst') and not name.startswith('docs/')]