Repository: jameslamb/pydistcheck (language: python)

Issue:
[new check] 'paths_exist'
### What should be checked?

Checks that specific paths exist in the archive.

This is common in `conda` recipes. For example, like this from `libpng`'s recipe.

```yaml
test:
  commands:
    - test -f ${PREFIX}/lib/libpng.a  # [not win]
    - test -f ${PREFIX}/lib/libpng${SHLIB_EXT}  # [not win]
```

([code link](https://github.com/conda-forge/libpng-feedstock/blob/4d757705628161203ecde86ffd393f7e348b429c/recipe/meta.yaml#L34-L37))

That's convenient in `conda` packaging because `conda build` allows you to run arbitrary commands in the build environment *before* those contents are zipped up into an archive. That works well in `conda` builds because there's a well defined place where just the files that'll be in the package are staged ([see the conda docs](https://docs.conda.io/projects/conda-build/en/stable/resources/define-metadata.html#test-section)).

In building wheels and sdists, that's not typically true. You usually invoke tools like `pip wheel`, `python -m build`, `poetry build`, etc. in a source tree that contains a mix of files that'll end up in the package and those that don't, and then when that command exits, an archive has been created.

I think it'd be useful for `pydistcheck` to run such tests, **especially** for wheels and sdists. No more having to manually verify that your combination of `MANIFEST.in`, `package_data`, `setup.py`, and PEP-517 backend are doing what you expect 😁 

### What should the name of this check be?

expected-paths-not-found

### Will this check introduce any additional configuration?

yes

### Details on additional configuration required.

This should probably be empty and do nothing by default. `pydistcheck` is intended to not be *too* opinionated about the particular way that Python packages are laid out or even really to specifically couple too tightly to only checking strictly "python" packages.

From the command line, this should be supported via an option that can be specified multiple times, like this:

```shell
pydistcheck \
   --expect-path 'lib/whatever.so' \
   --expect-path 'LICENSE' \
   --expect-path '__init__.py'
```

In `pyproject.toml`, it should be something like this:

```toml
[tool.pydistcheck]
expect_path = [
    "lib/whatever.so",
    "LICENSE",
    "__init__.py"
]
```

I don't think this should support globbing (at least in the first implementation).

### Distribution type

- [X] source (e.g. `.tar.gz`, `.zip`)
- [X] built (e.g. `.conda`, `.tar.bz2`, `.whl`)

### Notes

Special consideration will need to be given for the `.conda` format, which is an archive that itself contains other specially-named archives. Like do those intermediate archive names need to be in the paths? (I think no)

If it's not going to support globs, then does this need to support some type of platform markers like conda does with e.g. `# [win]`? If not, what do you do if you want to use the same configuration but on different platforms, where e.g. you might have a `lib/whatever.dll` on Windows and `lib/whatever.so` on Linux?

Should this specific option from the command line cause the `pyproject.toml` configuration to be ignored (as all other configuration currently works)? Or should it be merged with whatever's in `pyproject.toml`?


Discussion / hints:

Fix patch:
diff --git a/.github/workflows/check-docs.yml b/.github/workflows/check-docs.yml
index a2d0e2b..1496760 100644
--- a/.github/workflows/check-docs.yml
+++ b/.github/workflows/check-docs.yml
@@ -21,7 +21,7 @@ jobs:
       - uses: conda-incubator/setup-miniconda@v3
         with:
           environment-file: docs/env.yml
-          activate-environment: pydistscheck-docs
+          activate-environment: pydistcheck-docs
           miniforge-variant: Mambaforge
           use-mamba: true
       - name: build docs
diff --git a/docs/_static/defaults.toml b/docs/_static/defaults.toml
index 2202bfb..07c1337 100644
--- a/docs/_static/defaults.toml
+++ b/docs/_static/defaults.toml
@@ -1,10 +1,32 @@
 [tool.pydistcheck]
 
+# List of checks to ignore.
+# See https://pydistcheck.readthedocs.io/en/latest/check-reference.html for
+# a complete list.
 ignore = []
+
+# Set to true to print a summary of the distribution, like its total
+# size and largest files.
 inspect = false
+
+# If more than this many files is found in the distribution,
+# pydistcheck reports a 'too-many-files' check failure.
 max_allowed_files = 2000
+
+# If the distribution is larger than this size (compressed),
+# pydistcheck reports a 'distro-too-large-compressed' check failure.
+#
+# See 'pydistcheck --help' for available units.
 max_allowed_size_compressed = '50M'
+
+# If the distribution is larger than this size (uncompressed),
+# pydistcheck reports a 'distro-too-large-compressed' check failure.
+#
+# See 'pydistcheck --help' for available units.
 max_allowed_size_uncompressed = '75M'
+
+# List of fnmatch.fnmatchcase() patterns to be compared to directories
+# in the distribution.
 unexpected_directory_patterns = [
     '*/.appveyor',
     '*/.binder',
@@ -15,6 +37,9 @@ unexpected_directory_patterns = [
     '*/.pytest_cache',
     '*/.mypy_cache'
 ]
+
+# List of fnmatch.fnmatchcase() patterns to be compared to files
+# in the distribution.
 unexpected_file_patterns = [
     '*/appveyor.yml',
     '*/.appveyor.yml',
diff --git a/docs/configuration.rst b/docs/configuration.rst
index 04a90d7..7508e78 100644
--- a/docs/configuration.rst
+++ b/docs/configuration.rst
@@ -3,12 +3,14 @@ Configuration
 
 This page describes how to configure ``pydistcheck``.
 
-``pydistscheck`` resolves different sources of configuration in the following order.
+``pydistcheck`` combines different sources of configuration in the following order.
 
 1. default values
-2. :ref:`pyproject-toml`
+2. :ref:`pyproject-toml` (or custom TOML file passed via ``--config``)
 3. :ref:`cli-arguments`
 
+Configuration found further down the list overrides configuration found earlier in the list.
+
 .. toctree::
    :maxdepth: 2
    :caption: Contents:
diff --git a/docs/env.yml b/docs/env.yml
index feaa8d4..c545634 100644
--- a/docs/env.yml
+++ b/docs/env.yml
@@ -1,5 +1,5 @@
 ---
-name: pydistscheck-docs
+name: pydistcheck-docs
 channels:
   - nodefaults
   - conda-forge
diff --git a/src/pydistcheck/cli.py b/src/pydistcheck/cli.py
index 2776112..5cce5e8 100644
--- a/src/pydistcheck/cli.py
+++ b/src/pydistcheck/cli.py
@@ -69,7 +69,9 @@ class ExitCodes:
     is_flag=True,
     show_default=False,
     default=_Config.inspect,
-    help="print diagnostic information about the distribution",
+    help=(
+        "Print a summary of the distribution, like its total size and largest files."
+    ),
 )
 @click.option(  # type: ignore[misc]
     "--max-allowed-files",


Test patch:
diff --git a/.github/workflows/smoke-tests.yml b/.github/workflows/smoke-tests.yml
index d5dd911..69a2d05 100644
--- a/.github/workflows/smoke-tests.yml
+++ b/.github/workflows/smoke-tests.yml
@@ -32,7 +32,7 @@ jobs:
           fetch-depth: 1
       - uses: conda-incubator/setup-miniconda@v3
         with:
-          activate-environment: pydistscheck-tests
+          activate-environment: pydistcheck-tests
           miniforge-variant: Mambaforge
           use-mamba: true
           python-version: ${{ matrix.python_version }}
diff --git a/.github/workflows/unit-tests.yml b/.github/workflows/unit-tests.yml
index ee90175..dc3d2e6 100644
--- a/.github/workflows/unit-tests.yml
+++ b/.github/workflows/unit-tests.yml
@@ -56,7 +56,7 @@ jobs:
           fetch-depth: 1
       - uses: conda-incubator/setup-miniconda@v3
         with:
-          activate-environment: pydistscheck-tests
+          activate-environment: pydistcheck-tests
           miniforge-variant: Mambaforge
           use-mamba: true
           python-version: ${{ matrix.python_version }}