Repository: pypa/setuptools

Issue:
[FR] Warn when invalid table `[tools.setuptools]` is present in `pyproject.toml`
### What's the problem this feature will solve?

It is pretty easy to incorrectly spell the name of the table where `setuptools` metadata is declared, i.e. writing:

```toml
[tools.setuptools]
```

instead of:

```toml
[tool.setuptools]
```

And when `setuptools` metadata is not recognized because of this, it can be a slightly mysterious failure, [even for experts](https://mastodon.social/@gvwilson/111539718419834970), because the errant section is silently ignored.

### Describe the solution you'd like

It would be nice if `setuptools` issued a warning when the invalid `[tools.setuptools]` table is present in a project's metadata, to provide a hint for users that their metadata has a typo.

### Alternative Solutions

_No response_

### Additional context

This has cropped up in #3803, and #3683 also includes an incidental example of this spelling error. I can also say that I have made this mistake multiple times. It feels like the kind of issue you only know to avoid when you've stubbed your toe on it a few times.

### Code of Conduct

- [X] I agree to follow the PSF Code of Conduct


Discussion:

Resolving patch:
diff --git a/setuptools/config/pyprojecttoml.py b/setuptools/config/pyprojecttoml.py
--- a/setuptools/config/pyprojecttoml.py
+++ b/setuptools/config/pyprojecttoml.py
@@ -108,6 +108,10 @@ def read_configuration(
     if not asdict or not (project_table or setuptools_table):
         return {}  # User is not using pyproject to configure setuptools
 
+    if "setuptools" in asdict.get("tools", {}):
+        # let the user know they probably have a typo in their metadata
+        _ToolsTypoInMetadata.emit()
+
     if "distutils" in tool_table:
         _ExperimentalConfiguration.emit(subject="[tool.distutils]")
 
@@ -439,3 +443,9 @@ class _ExperimentalConfiguration(SetuptoolsWarning):
         "`{subject}` in `pyproject.toml` is still *experimental* "
         "and likely to change in future releases."
     )
+
+
+class _ToolsTypoInMetadata(SetuptoolsWarning):
+    _SUMMARY = (
+        "Ignoring [tools.setuptools] in pyproject.toml, did you mean [tool.setuptools]?"
+    )


Test patch:
diff --git a/setuptools/tests/config/test_pyprojecttoml.py b/setuptools/tests/config/test_pyprojecttoml.py
--- a/setuptools/tests/config/test_pyprojecttoml.py
+++ b/setuptools/tests/config/test_pyprojecttoml.py
@@ -8,6 +8,7 @@
 from path import Path
 
 from setuptools.config.pyprojecttoml import (
+    _ToolsTypoInMetadata,
     read_configuration,
     expand_configuration,
     apply_configuration,
@@ -369,3 +370,28 @@ def test_include_package_data_in_setuppy(tmp_path):
     assert dist.get_name() == "myproj"
     assert dist.get_version() == "42"
     assert dist.include_package_data is False
+
+
+def test_warn_tools_typo(tmp_path):
+    """Test that the common ``tools.setuptools`` typo in ``pyproject.toml`` issues a warning
+
+    See https://github.com/pypa/setuptools/issues/4150
+    """
+    config = """
+    [build-system]
+    requires = ["setuptools"]
+    build-backend = "setuptools.build_meta"
+
+    [project]
+    name = "myproj"
+    version = '42'
+
+    [tools.setuptools]
+    packages = ["package"]
+    """
+
+    pyproject = tmp_path / "pyproject.toml"
+    pyproject.write_text(cleandoc(config), encoding="utf-8")
+
+    with pytest.warns(_ToolsTypoInMetadata):
+        read_configuration(pyproject)
diff --git a/setuptools/tests/test_build_py.py b/setuptools/tests/test_build_py.py
--- a/setuptools/tests/test_build_py.py
+++ b/setuptools/tests/test_build_py.py
@@ -349,7 +349,7 @@ class TestTypeInfoFiles:
             name = "foo"
             version = "1"
 
-            [tools.setuptools]
+            [tool.setuptools]
             include-package-data = false
             """
         ),
@@ -359,7 +359,7 @@ class TestTypeInfoFiles:
             name = "foo"
             version = "1"
 
-            [tools.setuptools]
+            [tool.setuptools]
             include-package-data = false
 
             [tool.setuptools.exclude-package-data]
@@ -409,7 +409,14 @@ class TestTypeInfoFiles:
     }
 
     @pytest.mark.parametrize(
-        "pyproject", ["default_pyproject", "dont_include_package_data"]
+        "pyproject",
+        [
+            "default_pyproject",
+            pytest.param(
+                "dont_include_package_data",
+                marks=pytest.mark.xfail(reason="pypa/setuptools#4350"),
+            ),
+        ],
     )
     @pytest.mark.parametrize("example", EXAMPLES.keys())
     def test_type_files_included_by_default(self, tmpdir_cwd, pyproject, example):