Repository: pypa/setuptools

Issue:
[BUG] When specifying dynamic version using a single file, '/' is included in SOURCES.txt
### setuptools version

setuptools==66.1.0

### Python version

Python 3.10

### OS

macOS

### Additional environment information

_No response_

### Description

I am trying to use dynamic metadata ([as described in the docs here](https://setuptools.pypa.io/en/latest/userguide/pyproject_config.html#dynamic-metadata)) to load the version of my package in `pyproject.toml` from a file. I first tried using `version = { file = "src/VERSION" }`, and I found that if i do this, setuptools includes `/` in the `SOURCES.txt`; however, pip/setuptools can't handle this and will fail to build the package when installing it from source. 

I've attached an MWE ([sample_project.zip](https://github.com/pypa/setuptools/files/10467971/sample_project.zip)), but the key files are below:

My `pyproject.toml`:
```TOML
[project]
name = "sample_project"
dynamic = ["version"]

## build specification
[build-system]
requires = ["setuptools >= 61.0.0"]
build-backend = "setuptools.build_meta"

[tool.setuptools.dynamic]
version = {file = "src/VERSION"}
```

Contents of `src/VERSION`:
```
0.1.0
```


After some trial and error, a colleague of mine identified that if we make `file` into a list, the problem goes away:
```TOML
[tool.setuptools.dynamic]
version = {file = ["src/VERSION"]}
```

Looking in the documentation, i see other examples of files being specified as lists (e.g. `requirements.txt` dynamic loading) but there is no example of specifying the version using a file.

### Expected behavior

I could imagine two potential outcomes that would make sense in my head

1. `setuptools` metadata interprets `{file = "src/VERSION"}` the same as `{file = ["src/VERSION"]}`
2. `setuptools` throws an error if it does not get a list. To me, this is less intuitive (i can't imagine why anyone would need more than one file to specify the version) however it does provide quicker feedback about what is wrong and how to fix it.


### How to Reproduce

1. download and unzip the attached MWE and `cd` into the directory
2. install `build`: `pip install build`
3. run the build command: `python -m build --sdist`
4. (in a virtualenv) install the resulting sdist: `pip install dist/sample_project-0.1.0.tar.gz`

### Output

Output of step 3 (note `'/' not a regular file -- skipping` 2/3 of the way down):
```console
$ python -m build --sdist
* Creating venv isolated environment...
* Installing packages in isolated environment... (setuptools >= 61.0.0)
* Getting build dependencies for sdist...
/private/var/folders/fx/35b0z9051xz_8hmylnf2j4b80000gn/T/build-env-sjxd0z_i/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py:108: _BetaConfiguration: Support for `[tool.setuptools]` in `pyproject.toml` is still *beta*.
  warnings.warn(msg, _BetaConfiguration)
running egg_info
writing src/sample_project.egg-info/PKG-INFO
writing dependency_links to src/sample_project.egg-info/dependency_links.txt
writing top-level names to src/sample_project.egg-info/top_level.txt
reading manifest file 'src/sample_project.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'src/sample_project.egg-info/SOURCES.txt'
* Building sdist...
/private/var/folders/fx/35b0z9051xz_8hmylnf2j4b80000gn/T/build-env-sjxd0z_i/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py:108: _BetaConfiguration: Support for `[tool.setuptools]` in `pyproject.toml` is still *beta*.
  warnings.warn(msg, _BetaConfiguration)
running sdist
running egg_info
writing src/sample_project.egg-info/PKG-INFO
writing dependency_links to src/sample_project.egg-info/dependency_links.txt
writing top-level names to src/sample_project.egg-info/top_level.txt
reading manifest file 'src/sample_project.egg-info/SOURCES.txt'
reading manifest template 'MANIFEST.in'
writing manifest file 'src/sample_project.egg-info/SOURCES.txt'
warning: sdist: standard file not found: should have one of README, README.rst, README.txt, README.md

running check
creating sample_project-0.1.0
creating sample_project-0.1.0/src
creating sample_project-0.1.0/src/sample_project.egg-info
copying files to sample_project-0.1.0...
copying MANIFEST.in -> sample_project-0.1.0
copying pyproject.toml -> sample_project-0.1.0
'/' not a regular file -- skipping
copying src/VERSION -> sample_project-0.1.0/src
copying src/__init__.py -> sample_project-0.1.0/src
copying src/sample_project.egg-info/PKG-INFO -> sample_project-0.1.0/src/sample_project.egg-info
copying src/sample_project.egg-info/SOURCES.txt -> sample_project-0.1.0/src/sample_project.egg-info
copying src/sample_project.egg-info/dependency_links.txt -> sample_project-0.1.0/src/sample_project.egg-info
copying src/sample_project.egg-info/top_level.txt -> sample_project-0.1.0/src/sample_project.egg-info
Writing sample_project-0.1.0/setup.cfg
Creating tar archive
removing 'sample_project-0.1.0' (and everything under it)
Successfully built sample_project-0.1.0.tar.gz
```

Contents of `dist/sample_project-0.1.0.tar.gz/src/sample_project.egg_info/SOURCES.txt` produced by step 3:
```txt
MANIFEST.in
pyproject.toml
/
src/VERSION
src/__init__.py
src/sample_project.egg-info/PKG-INFO
src/sample_project.egg-info/SOURCES.txt
src/sample_project.egg-info/dependency_links.txt
src/sample_project.egg-info/top_level.txt
```

Output of step 4:
```console
$ python -m pip install dist/sample_project-0.1.0.tar.gz
Looking in indexes: https://pypi.org/simple, https://dig-grid-artifactory.apps.ge.com/artifactory/api/pypi/opus-one-pypi-virtual/simple
Processing ./dist/sample_project-0.1.0.tar.gz
  Installing build dependencies ... done
  Getting requirements to build wheel ... done
  Installing backend dependencies ... done
  Preparing metadata (pyproject.toml) ... done
Building wheels for collected packages: sample-project
  Building wheel for sample-project (pyproject.toml) ... error
  error: subprocess-exited-with-error
  
  × Building wheel for sample-project (pyproject.toml) did not run successfully.
  │ exit code: 1
  ╰─> [22 lines of output]
      /private/var/folders/fx/35b0z9051xz_8hmylnf2j4b80000gn/T/pip-build-env-wlxo9tub/overlay/lib/python3.10/site-packages/setuptools/config/pyprojecttoml.py:108: _BetaConfiguration: Support for `[tool.setuptools]` in `pyproject.toml` is still *beta*.
        warnings.warn(msg, _BetaConfiguration)
      running bdist_wheel
      running build
      running build_py
      creating build
      creating build/lib
      copying src/__init__.py -> build/lib
      running egg_info
      writing src/sample_project.egg-info/PKG-INFO
      writing dependency_links to src/sample_project.egg-info/dependency_links.txt
      writing top-level names to src/sample_project.egg-info/top_level.txt
      reading manifest file 'src/sample_project.egg-info/SOURCES.txt'
      reading manifest template 'MANIFEST.in'
      writing manifest file 'src/sample_project.egg-info/SOURCES.txt'
      error: Error: setup script specifies an absolute path:
      
          /
      
      setup() arguments must *always* be /-separated paths relative to the
      setup.py directory, *never* absolute paths.
      
      [end of output]
  
  note: This error originates from a subprocess, and is likely not a problem with pip.
  ERROR: Failed building wheel for sample-project
Failed to build sample-project
ERROR: Could not build wheels for sample-project, which is required to install pyproject.toml-based projects
```



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
@@ -309,10 +309,12 @@ def _ensure_previously_set(self, dist: "Distribution", field: str):
     def _expand_directive(
         self, specifier: str, directive, package_dir: Mapping[str, str]
     ):
+        from setuptools.extern.more_itertools import always_iterable  # type: ignore
+
         with _ignore_errors(self.ignore_option_errors):
             root_dir = self.root_dir
             if "file" in directive:
-                self._referenced_files.update(directive["file"])
+                self._referenced_files.update(always_iterable(directive["file"]))
                 return _expand.read_files(directive["file"], root_dir)
             if "attr" in directive:
                 return _expand.read_attr(directive["attr"], package_dir, root_dir)


Test patch:
diff --git a/setuptools/tests/test_sdist.py b/setuptools/tests/test_sdist.py
--- a/setuptools/tests/test_sdist.py
+++ b/setuptools/tests/test_sdist.py
@@ -502,7 +502,7 @@ def test_sdist_with_latin1_encoded_filename(self):
         "setup.cfg - long_description and version": """
             [metadata]
             name = testing
-            version = file: VERSION.txt
+            version = file: src/VERSION.txt
             license_files = DOWHATYOUWANT
             long_description = file: README.rst, USAGE.rst
             """,
@@ -513,7 +513,16 @@ def test_sdist_with_latin1_encoded_filename(self):
             license = {file = "DOWHATYOUWANT"}
             dynamic = ["version"]
             [tool.setuptools.dynamic]
-            version = {file = ["VERSION.txt"]}
+            version = {file = ["src/VERSION.txt"]}
+            """,
+        "pyproject.toml - directive with str instead of list": """
+            [project]
+            name = "testing"
+            readme = "USAGE.rst"
+            license = {file = "DOWHATYOUWANT"}
+            dynamic = ["version"]
+            [tool.setuptools.dynamic]
+            version = {file = "src/VERSION.txt"}
             """
     }
 
@@ -521,7 +530,8 @@ def test_sdist_with_latin1_encoded_filename(self):
     def test_add_files_referenced_by_config_directives(self, tmp_path, config):
         config_file, _, _ = config.partition(" - ")
         config_text = self._EXAMPLE_DIRECTIVES[config]
-        (tmp_path / 'VERSION.txt').write_text("0.42", encoding="utf-8")
+        (tmp_path / 'src').mkdir()
+        (tmp_path / 'src/VERSION.txt').write_text("0.42", encoding="utf-8")
         (tmp_path / 'README.rst').write_text("hello world!", encoding="utf-8")
         (tmp_path / 'USAGE.rst').write_text("hello world!", encoding="utf-8")
         (tmp_path / 'DOWHATYOUWANT').write_text("hello world!", encoding="utf-8")
@@ -536,9 +546,14 @@ def test_add_files_referenced_by_config_directives(self, tmp_path, config):
         with quiet():
             cmd.run()
 
-        assert 'VERSION.txt' in cmd.filelist.files
+        assert (
+            'src/VERSION.txt' in cmd.filelist.files
+            or 'src\\VERSION.txt' in cmd.filelist.files
+        )
         assert 'USAGE.rst' in cmd.filelist.files
         assert 'DOWHATYOUWANT' in cmd.filelist.files
+        assert '/' not in cmd.filelist.files
+        assert '\\' not in cmd.filelist.files
 
     def test_pyproject_toml_in_sdist(self, tmpdir):
         """