Repository: pypa/setuptools

Issue:
Please allow "file:" for setup.cfg install_requires
The new declarative setup.cfg syntax is fabulous.  It's wonderful to have two-line setup.py files!

I tried to use "install_requires = file: requirements/base.in" and found that "file:" wasn't supported there.  Our pip-compile workflow works really well, and it would be great to be able to start with base.in as our source of truth, and easily use it in our setup.cfg.

As a design point, I'm not sure it makes sense to special-case which fields can use which kinds of info-gatherers.  Why can't I use "file:" for any field?  I can understand if the implementation requires it, but the more the developer can choose how to structure their own world, the better.


Discussion:
There are two things I'd like to address here:

> I tried to use "install_requires = file: requirements/base.in" and found that "file:" wasn't supported there. Our pip-compile workflow works really well, and it would be great to be able to start with base.in as our source of truth, and easily use it in our setup.cfg.

My intuition is that I think that supporting `file:` in this context would mainly serve to encourage people to use their (version-locked) `requirements.txt` for their `install_requires`, which I consider to be an anti-pattern, since it is almost certainly the case that this should go the other direction. If there were a linter for build configurations, I'd want "imports a requirements.txt" to be in there. If 99% of people are going to use this feature to enable an anti-pattern, and 1% have a legitimate use like @nedbat's, I am -1 on it. For me, `setup.py` exists to enable "I know what I'm doing" use cases like this. (Though #1805 throws a bit of a monkey wrench in this, since static requirements in `setup.py` would actually be treated differently from requirements read in from a file).

Regardless of how this turns out in `setuptools`, it would be really nice if `pip-compile` could be used with arbitrary projects rather than just `requirements.txt` files, since that would make it a lot easier for `install_requires` to be your source of truth from which a lock file is generated.

> As a design point, I'm not sure it makes sense to special-case which fields can use which kinds of info-gatherers. Why can't I use "file:" for any field? I can understand if the implementation requires it, but the more the developer can choose how to structure their own world, the better.

This is *probably* not a big deal for `file:` itself, but I am mildly worried about the idea of inventing a whole generic DSL for our "declarative" build files. The more complexity we add, the harder those files will be to actually parse or statically analyze, and eventually we'll get back into the situation we're in now, where we desperately want to move to more constrained build specs.

I would say I am -0 on both of these propositions. I'm instinctively conservative here and don't see a super compelling use case, though I will admit that for people like @nedbat who have many different contextual dependencies, it makes sense to provide them some ability to refactor and avoid having a huge monolithic configuration file.

One possible compromise here could be supporting `file:`, but throwing an error if `==` is specified in the included file. Surely this would be frustrating for people who want to use their locked `requirements.txt` file, but if the main reason we're *not* supporting `file:` *at all* is to frustrate people who want to reuse their locked `requirements.txt` file, this might be a lower-impact way to do that that solves the "anti-pattern" portion of the issue.
> My intuition is that I think that supporting file: in this context would mainly serve to encourage people to use their (version-locked) requirements.txt for their install_requires, which I consider to be an anti-pattern ... 

This is something you should expect to see more of now with pipx simplifying the installation of Python CLIs from PyPI.  It _is_ an anti-pattern in the context of libraries but the reality is setuptools and PyPI are also used for application delivery.
I realized that this is a duplicate of #1074, which was closed as wontfix.

@jaraco Do you think anything has changed since #1074?
Just because a feature can be mis-used does not mean it should be forbidden.  I have a reason to use this, and I know what I am doing.  Is it better that I hack together something with sed and make?
> Do you think anything has changed since #1074?

In my mind, things have changed as there is a more compelling use-case presented above.

I'd be open to a PR that (a) adds support for `file:` in this field and (b) documents officially the caveat that packagers should resist the temptation to simply reference `requirements.txt` _especially_ if that file contains pinned requirements.

> reality is setuptools [is] used for application delivery

I would recommend that if pinning is done, it should be done outside of setuptools using tools like `pip-compile` and that the underlying package (even if a final deployment of a local application) should specify its abstract requirements and not some set of concrete requirements, but I won't go as far as to disallow that usage.

I don't think the `pipx` example is a good one, as applications like `tox` and `twine` shouldn't have pinned requirements for the same reason that libraries shouldn't have pinned requirements--the end user may have different demands than the application had at the time it was last released (including honoring environmental variation).
> Just because a feature can be mis-used does not mean it should be forbidden.

I agree that that's true, but it really depends on baseline rates. If we add this and for 99.9% of people it is mis-used and it's still *possible* to accomplish it without this feature, I think we're doing people a disservice by giving them a feature that's so easy to misuse.

I think that's the case here except that encouraging people to use `setup.py` for their "escape hatch" in this scenario may impact our ability to provide guarantees about deterministic dependencies.

It seems like the safest way to do this would be to allow `setup.cfg` to parse a file in a format that `pip` doesn't understand (so there's no danger of using it with a `requirements.txt`), and try and get `pip-compile` changed to allow for compiling `.` and `.[extras]` (and possibly "just the dependencies of `.`").
> It seems like the safest way to do this would be to allow setup.cfg to parse a file in a format that pip doesn't understand (so there's no danger of using it with a requirements.txt), and try and get pip-compile changed to allow for compiling . and .[extras] (and possibly "just the dependencies of .").

I don't think `pip-compile` users would see the need to specify their dependencies in a separate file in a format exclusive to `setup.cfg` if `pip-compile` were able to extract dependencies from pip requirement specifiers.  Would pinned dependencies be forbidden in this format or are you banking on obscurity?
> I don't think `pip-compile` users would see the need to specify their dependencies in a separate file in a format exclusive to `setup.cfg` if `pip-compile` were able to extract dependencies from pip requirement specifiers.

Not sure what you mean by "extract dependencies from pip requirement specifiers", but if you look at Ned's original use case I could definitely imagine someone with that many separate extras dependencies not wanting to combine those into one huge `setup.cfg`. I suppose the two feature requests are separable.

> Would pinned dependencies be forbidden in this format or are you banking on obscurity?

I think neither of these are the case. The problem to solve here is that a lot of people don't understand the difference between `requirements.txt` and `install_requires` and want to "single source" their install metadata by having `setuptools` pull data from their `requirements.txt`. If we make it *easy* to do that, people will just do it, not realizing that it's the wrong thing to do. We want the right thing to do to be easy and the right thing to do to be hard (we don't need to make it impossible).

As long as it's not easy to output the `setup.cfg` format with `pip freeze` or `pip-compile`, and it's not easy to `pip install` the `setup.cfg` format other than through the normal mechanism of `pip install .`, then the design objective is satisfied. Sure, someone could write a converter between the two things, but hopefully when people google, "How do I convert between `requirements.txt` and `setuptools` requirements file?" they will find some documentation saying, "You shouldn't, that's why they have two different formats."

As an aside, this reminds me of the [Susan B. Anthony Dollar](https://en.wikipedia.org/wiki/Susan_B._Anthony_dollar), which was a US$1 coin that looks almost identical to a quarter ($0.25). People complained because it was very easy to confuse the two and give someone $4 when you meant to give them $1. It was such a similar shape and size that you could even put the $1 coins in vending machines designed for $0.25 coins. My reasoning for using a different format is similar to the reason for making $1 coins a different color and size than $0.25 coins: `install_requires` and `requirements.txt` serve different purposes and we don't want to confuse people by making them similar enough that a lot of tooling almost (but not always) "works".

This is just an idea, though. I'm willing to be persuaded otherwise.
> Not sure what you mean by "extract dependencies from pip requirement specifiers" ...

I mean these: `.` and `.[extras]`.  There is an open issue on the `pip-tools` tracker to support build system-agnostic requirements, meaning 'locked' requirements can be compiled from any pip-installable package.

> ... but if you look at Ned's original use case I could definitely imagine someone with that many separate extras dependencies not wanting to combine those into one huge setup.cfg. I suppose the two feature requests are separable.

If no other program is able to interoperate with the new file - the only benefit there being to reduce the size of `setup.cfg` - I think it'd fall kind of flat.  `readme` and `version` take a `file:` value because single sourcing the readme and version number _is_ desirable.

> The problem to solve here is that a lot of people don't understand the difference between requirements.txt and install_requires ...

I think a lot of people don't appreciate that these are different formats.  That you are able to pipe a `requirements.txt` into `install_requires` and have it work (for the most part) appears to be an accident of history.  But do a lot of people who work with `setuptools` (package authors) not understand that pinning their dependencies is poor practice?  I'm not so sure.
Another idea: pip-compile can read source requirements from `setup.py`, so it could be extended to support `setup.cfg` too.
Note: `requirements.in` and `requirements.txt` are substantially different: the `*.in`  file doesn't pin all versions as a rule otherwise there would be no need to `pip-compile` it into `requirements.txt`.

I understand the logic of avoiding `requirements.txt` in `setup.cfg` but what is the argument against: `install_requires = file: requirements.in`?
CI+caching is another usecase where this would be useful: I'd like to cache my venv folder (using e.g. the hash of requirements.txt as a cache key), but right now this means i have to duplicate the requirements in setup.cfg and requirements.txt for this to work.
I too have the use case where I want to reuse my `.in` requirements for packaging while I also maintain a locked `.txt` file for CI purposes.

At the bare minimum, it would be awesome if setuptools provided an entry point so that a third party library could read requirements files and set the various `_require` on the distribution instance, similar to how `setuptools_scm` sets the version attribute. In this case, setuptools would not be "responsible" for the misuse, and that third party library would.

Would you be open for said entry-point?
So, for those interested, I managed to hack something which might also help you, https://pypi.org/project/setuptools-declarative-requirements
> Another idea: pip-compile can read source requirements from `setup.py`, so it could be extended to support `setup.cfg` too.

Some more info on this: if you have a `setup.py` that gets its `install_requires` from `setup.cfg`, this seems to already work (did a quick test with `pip-compile` 5.3.1), and there seems to already be an issue to support `setup.cfg` directly (https://github.com/jazzband/pip-tools/issues/1047).
> Another idea: pip-compile can read source requirements from `setup.py`, so it could be extended to support `setup.cfg` too.

I'd take this a step further and argue that `pip-compile` (and other projects that need to inspect the requirements for the source of a project) should use [pep517.meta.load](https://github.com/pypa/pep517/blob/dc1192dd7cf7f9d2a295a3f1f1c93ab659f8556b/pep517/meta.py#L62-L72) to extract/parse metadata (and requirements). Such an approach would not only support setup.py and setup.cfg files, but would also support extras and builders other than setuptools.

Example:

```
tempora master $ pip-run -q pep517 -- -c "from pep517 import meta; print(meta.load('.').metadata.get_all('Requires-Dist'))"
['pytz', 'jaraco.functools (>=1.20)', "sphinx ; extra == 'docs'", "jaraco.packaging (>=3.2) ; extra == 'docs'", "rst.linker (>=1.9) ; extra == 'docs'", "pytest (!=3.7.3,>=3.5) ; extra == 'testing'", "pytest-checkdocs (>=1.2.3) ; extra == 'testing'", "pytest-flake8 ; extra == 'testing'", "pytest-cov ; extra == 'testing'", "jaraco.test (>=3.2.0) ; extra == 'testing'", "backports.unittest-mock ; extra == 'testing'", "freezegun ; extra == 'testing'", "pytest-freezegun ; extra == 'testing'", 'pytest-black (>=0.3.7) ; (platform_python_implementation != "PyPy") and extra == \'testing\'', 'pytest-mypy ; (platform_python_implementation != "PyPy") and extra == \'testing\'']
```
> CI+caching is another usecase where this would be useful: I'd like to cache my venv folder (using e.g. the hash of requirements.txt as a cache key), but right now this means i have to duplicate the requirements in setup.cfg and requirements.txt for this to work.

Same thing here: preinstalling dependencies in a container image (that's rebuilt when dependencies change) can yield <10s CI times (incl. testing on all supported versions of CPython, linting, etc.) on most of my projects, but doing that in a sensible way requires having the list of dependencies in a separate file.

Quick CI times can do a lot for project health and developer experience:
- waiting on CI means contributors are more tempted to stuff somewhat-unrelated changes in the same PR;
- waiting also interrupts the “flow”, forcing the developer to either stop coding (forcing them out of the productive headspace if they reached it) or juggle more concurrent PRs (higher cognitive load, risk of merge conflicts, ...)

I know it's perfectly [feasible] with `setup.py`, but given that it's pretty-much always the one thing that requires writing that file, `file:` support in the `*_requires` options would let many projects get rid of `setup.py` altogether.

[feasible]: https://github.com/ppb/ppb-vector/blob/canon/setup.py#L5-L22
PS: I just [added to bork] (a dev/release automation tool for Python projects) the ability to dump the list of dependencies, using `pep517.meta` as per @jaraco's [comment].  This is far from a complete solution, but at least it will be a stopgap for Bork users.


[added to bork]: https://github.com/duckinator/bork/pull/241
[comment]: https://github.com/pypa/setuptools/issues/1951#issuecomment-722645440
For the `pip-compile` use case, it's now possible to compile from `setup.cfg`, E.g.,

```ini
# setup.cfg
[metadata]
name = my-package
# ...
[options]
install_requires =
    requests>2,<3

[options.extras_require]
dev =
    mypy
    types-requests
```

```console
$ pip-compile --extra dev --output-file requirements-dev.txt setup.cfg
```

Resulting in:
```
#
# This file is autogenerated by pip-compile with python 3.9
# To update, run:
#
#    pip-compile --extra=dev --output-file=requirements-dev.txt setup.cfg
#

certifi==2021.5.30
    # via requests
chardet==4.0.0
    # via requests
idna==2.10
    # via requests
mypy==0.910
    # via my-package (setup.cfg)
mypy-extensions==0.4.3
    # via mypy
requests==2.25.1
    # via my-package (setup.cfg)
toml==0.10.2
    # via mypy
types-requests==2.25.0
    # via my-package (setup.cfg)
typing-extensions==3.10.0.0
    # via mypy
urllib3==1.26.6
    # via requests
```
I was looking for this feature for years but since pip-compile got support for reading setup.cfg, I am no longer sure how critical this is. Once of the deep PITA remaining is that dependabot team failed to add support for reading setup.cfg, ignoring what is in fact the recommended way to record dependencies for python projects. Also I was not able to find a tool that can properly update setup.cfg with new requirements, maybe some watching this ticket know one.

If support for `file:` is added, it should also be added for extras, or we would endup with requirements in multiple places in multiple formats.

 My use case for pip-compile is bit different than the original one, I am using it to build `constraints.txt` file which lock everything, including the test dependencies, so we can be sure that two runs on CI/CD or local would use the same libraries, making testing bit more predictable. Those constraints do not really end-up as effective package dependencies, as those are more relaxed using ranges manually updated inside setup.cfg files.
I don't understand why this is still being debated :|.

Let the developer provide install_requires = file: requirements.txt. What's the big deal?
Unfortunately setuptools-declarative-requirements is "broken" as of some recent changes in ecosystem (not sure of exact course) so that option is now less attractive as only works with absolute paths.
@adamcunnington Did you read the replies? The reason to about is set out in great detail. The TL;DR is that this enables a very common anti-pattern, and legitimate use cases are few and far between.
It's indeed TL;DR, that I didn't bother reading maintainers cry that people will use it the way the maintains don't want.

So, people will find workarounds, one is @s0undt3ch [`setuptools-declarative-requirements`](https://pypi.org/project/setuptools-declarative-requirements) package:
- https://github.com/pypa/setuptools/issues/1951#issuecomment-718094135

However, it's [currently broken](https://github.com/s0undt3ch/setuptools-declarative-requirements/issues/5#issuecomment-894671541), but [a workaround exists](https://github.com/s0undt3ch/setuptools-declarative-requirements/issues/5#issuecomment-894673665).

The worse scenario is that people go back to `setup.py` because `setup.cfg` is limiting, or don't even bother switching to `setup.cfg`.

What could be done, is to ensure there's good documentation for `install_requires = file: requirements.txt` what are the anti-patterns that should be avoided. Described in simple language that everybody understands, and offers solutions and explanations. and in fact, in project, there could be requirements.txt, requirements-tests.txt, requirements-dist.txt all for different purposes, so given the "file:" support, you can use it in a preferred way.
Seems like pip-compile supports or will support the valid use cases now, should we go ahead and close this @jaraco?
Just to chime in, I am also in favor of a simple and clear mechanism uniformly applied.  Mis-use shouldn't be a reason to hobble the feature.  My original request had a valid use, with 25 upvotes.  Having one setting that won't allow "file:" is confusing, and frankly, a bit condescending.
I think I agree with Ned that hobbling the feature doesn't feel appropriate. That is, I wouldn't mind seeing `file:` support for any field where the value could reasonably be expected to be supplied by substituting the content of a file. It's less obvious to me what the behavior should be when the file contains anything other than the items that would be supplied to 'install_requires'.

I also agree with Paul that this feature is likely to promote bad practices, that users will be tempted to use 'requirements.txt' to specify the dependencies, reference that from setup.cfg, inspire other users to follow that anti-pattern, and unintentionally create a swell of negative sentiment that the feature doesn't support the features one would expect for requirements.txt.

I'd be willing to give the feature a try on a provisional basis - the proviso being that the feature is not intended to add 'requirements.txt' support for Setuptools, but merely to provide a file-source for `install_requires` and that usage beyond that basic form is unsupported. And of course if the feature is causing an undue maintenance burden, it may be removed.

> If support for `file:` is added, it should also be added for extras.

That seems reasonable. So `docs = file:docs-requirements.txt` or similar.


Would someone be willing to draft a patch?

One thing to note is that [PEP 621](https://www.python.org/dev/peps/pep-0621/) does not have the option to include dependencies from a file, so this would have to be a setuptools-specific extension when we transition to PEP 621. If we were to support pulling dependencies from an arbitrary file, we'd need to specify that field as `dynamic` and either add a separate section for where to pull it from a file under the `tool:setuptools` key or require that people do this in `setup.py` (my preferred choice).
The output when a `file:` directive is in a spot where it is not allowed is an extremely unhelpful parser failed exception stack trace. 
A human-readable error message would be much appreciated.
Just throwing some extra weight in here and an alternate approach.

The packaging ecosystem is trying to converge on setup.cfg/pyproject.toml to define *what* your project needs for build, install, etc., and leave it up to the build tools to figure out how to get them.

I think it's a good idea to retire the requirements file -- I was never a fan of that approach anyway.

And, it's not even necessary - I just read the contents of Salt's pip-compile requirements.txt file into the `install_requires` of a setup.cfg and it worked just fine. Didn't even need to remove the comments.
> It's indeed TL;DR, that I didn't bother reading maintainers cry that people will use it the way the maintains don't want.
> 
> So, people will find workarounds, one is @s0undt3ch [`setuptools-declarative-requirements`](https://pypi.org/project/setuptools-declarative-requirements) package:
> 
> * [Please allow "file:" for setup.cfg install_requires #1951 (comment)](https://github.com/pypa/setuptools/issues/1951#issuecomment-718094135)
> 
> However, it's [currently broken](https://github.com/s0undt3ch/setuptools-declarative-requirements/issues/5#issuecomment-894671541), but [a workaround exists](https://github.com/s0undt3ch/setuptools-declarative-requirements/issues/5#issuecomment-894673665).
> 
> The worse scenario is that people go back to `setup.py` because `setup.cfg` is limiting, or don't even bother switching to `setup.cfg`.
> 
> What could be done, is to ensure there's good documentation for `install_requires = file: requirements.txt` what are the anti-patterns that should be avoided. Described in simple language that everybody understands, and offers solutions and explanations. and in fact, in project, there could be requirements.txt, requirements-tests.txt, requirements-dist.txt all for different purposes, so given the "file:" support, you can use it in a preferred way.

This is exactly where we are at the moment. Requirements.txt is just used for listing test packages while we just use the setup.cfg install_requires for the build step (which excludes pytest et al.). It is however handy to list the required packages in a separate file and call it from setup.cfg. And I say this with a CICD automation in mind, where, generally speaking, the more you segregate in separate files (or even repos), the more efficient pipelines you can run. 
> > It's indeed TL;DR, that I didn't bother reading maintainers cry that people will use it the way the maintains don't want.
> > 
> > So, people will find workarounds, one is @s0undt3ch [`setuptools-declarative-requirements`](https://pypi.org/project/setuptools-declarative-requirements) package:
> > 
> > * [Please allow "file:" for setup.cfg install_requires #1951 (comment)](https://github.com/pypa/setuptools/issues/1951#issuecomment-718094135)
> > 
> > However, it's [currently broken](https://github.com/s0undt3ch/setuptools-declarative-requirements/issues/5#issuecomment-894671541), but [a workaround exists](https://github.com/s0undt3ch/setuptools-declarative-requirements/issues/5#issuecomment-894673665).
> > 
> > The worse scenario is that people go back to `setup.py` because `setup.cfg` is limiting, or don't even bother switching to `setup.cfg`.
> > 
> > What could be done, is to ensure there's good documentation for `install_requires = file: requirements.txt` what are the anti-patterns that should be avoided. Described in simple language that everybody understands, and offers solutions and explanations. and in fact, in project, there could be requirements.txt, requirements-tests.txt, requirements-dist.txt all for different purposes, so given the "file:" support, you can use it in a preferred way.
> 
> This is exactly where we are at the moment. Requirements.txt is just used for listing test packages while we just use the setup.cfg install_requires for the build step (which excludes pytest et al.). It is however handy to list the required packages in a separate file and call it from setup.cfg. And I say this with a CICD automation in mind, where, generally speaking, the more you segregate in separate files (or even repos), the more efficient pipelines you can run. 

It's not broken, at most undocumented.
The requirements files must be included in any packages built. I never ran across the problems because I also use setuptools_scm which takes care of that.

With that said, I'd love not to maintain a separate package and that the functionality would be built in.
Is there currently a way to make `setup.cfg` work with dependabot in the sense of allowing it to upgrade dependencies? I find it hilarious that dependabot is already able to recognize dependencies from inside `pyproject.toml` (PEP-631) while setuptools does not.

I recently migrated a dozen of projects to PEP-517 and succeeded to remove the `setup.py`, but now I realise that maybe that was too soon, as the newer setup.cfg is already on its way out, even if less that 1% of packages adopted it in the pure form.

I really want to avoid reinstating the `setup.py`...

Resolving patch:
diff --git a/setuptools/config/setupcfg.py b/setuptools/config/setupcfg.py
--- a/setuptools/config/setupcfg.py
+++ b/setuptools/config/setupcfg.py
@@ -568,15 +568,27 @@ def __init__(
         self.root_dir = target_obj.src_root
         self.package_dir: Dict[str, str] = {}  # To be filled by `find_packages`
 
+    @classmethod
+    def _parse_list_semicolon(cls, value):
+        return cls._parse_list(value, separator=';')
+
+    def _parse_file_in_root(self, value):
+        return self._parse_file(value, root_dir=self.root_dir)
+
+    def _parse_requirements_list(self, value):
+        # Parse a requirements list, either by reading in a `file:`, or a list.
+        parsed = self._parse_list_semicolon(self._parse_file_in_root(value))
+        # Filter it to only include lines that are not comments. `parse_list`
+        # will have stripped each line and filtered out empties.
+        return [line for line in parsed if not line.startswith("#")]
+
     @property
     def parsers(self):
         """Metadata item name to parser function mapping."""
         parse_list = self._parse_list
-        parse_list_semicolon = partial(self._parse_list, separator=';')
         parse_bool = self._parse_bool
         parse_dict = self._parse_dict
         parse_cmdclass = self._parse_cmdclass
-        parse_file = partial(self._parse_file, root_dir=self.root_dir)
 
         return {
             'zip_safe': parse_bool,
@@ -591,11 +603,11 @@ def parsers(self):
                 "consider using implicit namespaces instead (PEP 420).",
                 SetuptoolsDeprecationWarning,
             ),
-            'install_requires': parse_list_semicolon,
-            'setup_requires': parse_list_semicolon,
-            'tests_require': parse_list_semicolon,
+            'install_requires': self._parse_requirements_list,
+            'setup_requires': self._parse_list_semicolon,
+            'tests_require': self._parse_list_semicolon,
             'packages': self._parse_packages,
-            'entry_points': parse_file,
+            'entry_points': self._parse_file_in_root,
             'py_modules': parse_list,
             'python_requires': SpecifierSet,
             'cmdclass': parse_cmdclass,
@@ -682,8 +694,10 @@ def parse_section_extras_require(self, section_options):
 
         :param dict section_options:
         """
-        parse_list = partial(self._parse_list, separator=';')
-        parsed = self._parse_section_to_dict(section_options, parse_list)
+        parsed = self._parse_section_to_dict(
+            section_options,
+            self._parse_requirements_list,
+        )
         self['extras_require'] = parsed
 
     def parse_section_data_files(self, section_options):


Test patch:
diff --git a/setuptools/tests/config/test_setupcfg.py b/setuptools/tests/config/test_setupcfg.py
--- a/setuptools/tests/config/test_setupcfg.py
+++ b/setuptools/tests/config/test_setupcfg.py
@@ -884,6 +884,24 @@ def test_cmdclass(self, tmpdir):
             assert cmdclass.__module__ == "custom_build"
             assert module_path.samefile(inspect.getfile(cmdclass))
 
+    def test_requirements_file(self, tmpdir):
+        fake_env(
+            tmpdir,
+            DALS("""
+            [options]
+            install_requires = file:requirements.txt
+            [options.extras_require]
+            colors = file:requirements-extra.txt
+            """)
+        )
+
+        tmpdir.join('requirements.txt').write('\ndocutils>=0.3\n\n')
+        tmpdir.join('requirements-extra.txt').write('colorama')
+
+        with get_dist(tmpdir) as dist:
+            assert dist.install_requires == ['docutils>=0.3']
+            assert dist.extras_require == {'colors': ['colorama']}
+
 
 saved_dist_init = _Distribution.__init__