-
-
Notifications
You must be signed in to change notification settings - Fork 1.3k
support setup_requires
in setup.cfg
#1150
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
support setup_requires
in setup.cfg
#1150
Conversation
in light of pypa/setuptools-scm#181 - could this add a entry-point for the section keys as well? |
0e8c137
to
6fa3dd3
Compare
Amended to support the following use-case:
from setuptools import setup
setup(name='project')
[metadata]
version = attr: setuptools_scm.get_version
[options]
setup_requires =
setuptools_scm |
@benoit-pierre, thank you. I wonder why We end up with two parse steps essentially, but having in mind all the good it brings to us, I think it's not a big deal. So if this branch passes PyPI top test, we need @jaraco to review the implementation. |
@benoit-pierre that's exactly the way i didn't want to go about it, setuptools_scm should be configured, version shouldn't be set to just the function, that's just a mess |
@RonnyPfannschmidt |
@idlesign the main problem there is that the config parsing does not support extension via entrypoints - as long as one can't add the parameters one adds to setup calls via a entrypoints to the config parsing as well, its all moot |
and turning off error handling instead of addressing the underlying issue is just a problem |
Hm, this pull request in clearly not about entry points it's about |
@idlesign you clearly upvoted the amendment that happened to this pr to support the use case i never wanted to see supported, which is what i complained about a bigger problem for setup_requires is, that the keywords they add to setup.py aren't automatically supported in setup.cfg, which to me is the real problem/bummer in order to support that one needs the ability to add own parsers for sections/keys in the setuptools plugins setup.py keyword arguments are added via entrypoints, a similar mechanism would be needed for setup.cfg options added by setup_requires ... |
I can repeat: that's not about If you want to talk about |
@RonnyPfannschmidt: @idlesign is right, this PR is not about |
setuptools/__init__.py
Outdated
@@ -109,7 +109,10 @@ def _looks_like_package(path): | |||
|
|||
find_packages = PackageFinder.find | |||
|
|||
setup = distutils.core.setup | |||
def setup(**attrs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nitpick: IMHO it would look nicer if done via decorator rather than closure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is the added complexity worth it? I'll will set setup.__doc__
to distutils.core.setup.__doc__
, however.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Readability matters :)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
But that's my point: it's more complex, hence less readable.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I worry that this one seemingly compatible change will have some unexpected consequences. There are probably many projects that still use distutils.core.setup
directly, especially those that have been configured to run without setuptools entirely, but where pip
, by shim-installing setuptools, causes setuptools behavior to be included even when distutils.core.setup
is the function invoked by the setup script. For those packages, this new behavior will be bypassed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I review a little further, I see that this flag that's being set will only affect packages using setup.cfg, so those packages can necessarily be expected to be invoking setup from setuptools... and they simply won't get this new behavior if they're somehow invoking distutils.core.setup()
.
setuptools/__init__.py
Outdated
def setup(**attrs): | ||
if 'parse_config' not in attrs: | ||
attrs['parse_config'] = True | ||
distutils.core.setup(**attrs) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not sure what distutils.core.setup()
return type is, but I'd prepend this line with return
, just to be on the safe side.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The resulting distribution object should be returned. And this behavior is actually tested, except the tests are not picked up by pytest (because they are in setuptools/tests/__init__.py
)!
setuptools/dist.py
Outdated
@@ -361,6 +357,26 @@ def __init__(self, attrs=None): | |||
) | |||
self._finalize_requires() | |||
|
|||
def _install_setup_requires(self, attrs): | |||
self.dependency_links = attrs.get('dependency_links') |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes, using setup(dependency_links=[])
should override a corresponding dependency_links
setting in the configuration. If not passed as argument to setup()
or set in the configuration, a default value will still be set.
setuptools/dist.py
Outdated
): | ||
dist = self.__class__() | ||
dist.parse_config_files(ignore_option_errors=True) | ||
if self.setup_requires is None: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think that if both self.setup_requires
and dist.setup_requires
are not None
some warning is required, so that it will alert end-user who accidentally put the option into two places that only one is applied (and two lists are not merged).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That would mean always parsing the configuration. What about other options? When passed as arguments to setup()
, they always override a corresponding setting in setup.cfg
, no?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what about at least elif dist.setup_requires is not None:
here?
6fa3dd3
to
df0ae04
Compare
Rebased on #1157. |
Apparently, a setup.cfg's option will override its corresponding
from setuptools import setup
dist = setup(zip_safe=False, install_requires=[])
print('install_requires', dist.install_requires)
print('zip_safe', dist.zip_safe)
[options]
zip_safe = True
install_requires = foobar
Which I find pretty horrible... |
Yeap, this should be addressed separately. |
I would expect for setup arguments to always take precedence over setup.cfg. |
df0ae04
to
b870ca0
Compare
Anyway, amended to make the behavior consistent. Alternatively we could stop parsing the configuration during setuptools/__init__.py | 20 +++++++++++++++++++-
setuptools/dist.py | 30 +++++++++++++-----------------
2 files changed, 32 insertions(+), 18 deletions(-)
diff --git c/setuptools/__init__.py w/setuptools/__init__.py
index 04f76740..420ad7e5 100644
--- c/setuptools/__init__.py
+++ w/setuptools/__init__.py
@@ -109,7 +109,25 @@ class PEP420PackageFinder(PackageFinder):
find_packages = PackageFinder.find
-setup = distutils.core.setup
+
+def _install_setup_requires(attrs):
+ keep = '''
+ dependency_links setup_requires
+ '''.split()
+ dist = Distribution(dict(
+ (k, v) for k, v in attrs.items() if k in keep
+ ))
+ # Honor setup.cfg's options.
+ dist.parse_config_files(ignore_option_errors=True)
+ if dist.setup_requires:
+ dist.fetch_build_eggs(dist.setup_requires)
+
+def setup(**attrs):
+ # Make sure we have any requirements needed to interpret 'attrs'.
+ _install_setup_requires(attrs)
+ return distutils.core.setup(**attrs)
+setup.__doc__ = distutils.core.setup.__doc__
+
_Command = monkey.get_unpatched(distutils.core.Command)
diff --git c/setuptools/dist.py w/setuptools/dist.py
index a2ca8795..e46541ad 100644
--- c/setuptools/dist.py
+++ w/setuptools/dist.py
@@ -316,23 +316,19 @@ class Distribution(Distribution_parse_config_files, _Distribution):
have_package_data = hasattr(self, "package_data")
if not have_package_data:
self.package_data = {}
- _attrs_dict = attrs or {}
- if 'features' in _attrs_dict or 'require_features' in _attrs_dict:
+ attrs = attrs or {}
+ if 'features' in attrs or 'require_features' in attrs:
Feature.warn_deprecated()
self.require_features = []
self.features = {}
self.dist_files = []
- self.src_root = attrs and attrs.pop("src_root", None)
+ self.src_root = attrs.pop("src_root", None)
self.patch_missing_pkg_info(attrs)
- self.long_description_content_type = _attrs_dict.get(
+ self.long_description_content_type = attrs.get(
'long_description_content_type'
)
- # Make sure we have any eggs needed to interpret 'attrs'
- if attrs is not None:
- self.dependency_links = attrs.pop('dependency_links', [])
- assert_string_list(self, 'dependency_links', self.dependency_links)
- if attrs and 'setup_requires' in attrs:
- self.fetch_build_eggs(attrs['setup_requires'])
+ self.dependency_links = attrs.pop('dependency_links', [])
+ self.setup_requires = attrs.pop('setup_requires', [])
for ep in pkg_resources.iter_entry_points('distutils.setup_keywords'):
vars(self).setdefault(ep.name, None)
_Distribution.__init__(self, attrs)
@@ -427,14 +423,15 @@ class Distribution(Distribution_parse_config_files, _Distribution):
req.marker = None
return req
- def parse_config_files(self, filenames=None):
+ def parse_config_files(self, filenames=None, ignore_option_errors=False):
"""Parses configuration files from various levels
and loads configuration.
"""
_Distribution.parse_config_files(self, filenames=filenames)
- parse_configuration(self, self.command_options)
+ parse_configuration(self, self.command_options,
+ ignore_option_errors=ignore_option_errors)
self._finalize_requires()
def parse_command_line(self):
@@ -497,19 +494,18 @@ class Distribution(Distribution_parse_config_files, _Distribution):
"""Fetch an egg needed for building"""
from setuptools.command.easy_install import easy_install
dist = self.__class__({'script_args': ['easy_install']})
- dist.parse_config_files()
opts = dist.get_option_dict('easy_install')
keep = (
'find_links', 'site_dirs', 'index_url', 'optimize',
'site_dirs', 'allow_hosts'
)
- for key in list(opts):
- if key not in keep:
- del opts[key] # don't use any other settings
+ opts.update((k, v)
+ for k, v in self.get_option_dict('easy_install').items()
+ if k in keep)
if self.dependency_links:
links = self.dependency_links[:]
if 'find_links' in opts:
- links = opts['find_links'][1].split() + links
+ links = opts['find_links'][1] + links
opts['find_links'] = ('setup', links)
install_dir = self.get_egg_cache_dir()
cmd = easy_install( |
I like the second way better from the point of view of clarity. |
setuptools/__init__.py
Outdated
setup = distutils.core.setup | ||
def setup(**attrs): | ||
if 'parse_config' not in attrs: | ||
attrs['parse_config'] = True |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These two lines can be replaced by attrs.setdefault('parse_config', True)
.
setuptools/__init__.py
Outdated
@@ -109,7 +109,10 @@ def _looks_like_package(path): | |||
|
|||
find_packages = PackageFinder.find | |||
|
|||
setup = distutils.core.setup | |||
def setup(**attrs): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Now that I review a little further, I see that this flag that's being set will only affect packages using setup.cfg, so those packages can necessarily be expected to be invoking setup from setuptools... and they simply won't get this new behavior if they're somehow invoking distutils.core.setup()
.
setuptools/dist.py
Outdated
@@ -361,6 +357,20 @@ def __init__(self, attrs=None): | |||
) | |||
self._finalize_requires() | |||
|
|||
def _install_setup_requires(self, attrs): | |||
if attrs.pop('parse_config', False): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since this parameter is private and not meant for any user to invoke, it should probably be called _parse_config
or similar.
"""Parses configuration files from various levels | ||
and loads configuration. | ||
|
||
""" | ||
_Distribution.parse_config_files(self, filenames=filenames) | ||
|
||
parse_configuration(self, self.command_options) | ||
parse_configuration(self, self.command_options, | ||
ignore_option_errors=ignore_option_errors) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This use of the ignore_option_errors
flag concerns me. It bothered me in the config.py module, but now that complexity is tied to several method signatures in the Distribution, which is monkey patched into distutils. I don't yet have a better recommendation, however.
I like this idea a lot. This one aspect of setuptools, the forced injection of complex behavior (network access, downloading, building, installing in a temporary location) has created much consternation. Making this behavior explicit and decoupled from distutils behavior feels like the right move and would likely simplify many of these complicated scenarios. The aforementioned risk would still exist - packages invoking |
b870ca0
to
472c79f
Compare
I rebased on master and updated the patch to handle
With the following from distutils.core import setup
setup(name='pkga', version='1.0', setup_requires='barbazquux')
Considering the difference in behavior with master, and the fact that FWIW, I also run pip's full testsuite with this PR version of setuptools with no issues. |
Fix #1054.
The patch is not straightforward because: parsing
setup.cfg
is not part ofDistribution.__init__
(it's done later explicitly bydistutils.setup
), but installingsetup_requires
' requirements is (and this must be kept early, so all options passed tosetup
are correctly interpreted)... Additionally, instantiating aDistribution
object with no arguments should not raise an error if there's an invalidsetup.cfg
in the current directory.