Open dvzrv opened 2 years ago
Hi @dvzrv, thank you very much for reporting this.
I believe the following is happening:
egg_info
command as a step to create a sdist
.egg_info
uses configparser.ConfigParser.write
from Python's standard library, to store build-time information in the setup.cfg
file (e.g. version parts like tag_build
and tag_date
).configparser.ConfigParser.write
does not seem to preserve indentation spaces and instead replace them with tabs.I went through configparser's doc and I could not find a configuration that enforces configparser
to preserve the indentation.
I believe that before we implement any change in the setuptools, that needs to be implemented first[^1]. Please let me know if you have a different solution (also please feel free to submit a PR).
Meanwhile, I will reclassify this issue as a Feature Request and tag it as dependent of the configparser
behaviour.
[^1]: Please note that it is not in the scope of the setuptools project to implement a custom version of configparser
. We also want to minimize the number of dependencies as those have to be vendorized to avoid cycles during dependence resolution, so a solution from the stdlib would be ideal here.
@abravalheri hmm, so altering/extending the configparser behavior would be the ideal solution for this?
Hi @dvzrv , I would word it differently: without support for preserving indentation implemented in configparser
it is not viable for setuptools to support this feature.
when trying to apply patches
same here
patching file setup.cfg
Hunk #1 FAILED at 44.
1 out of 1 hunk FAILED -- saving rejects to file setup.cfg.rej
quickfix: convert tab-indent back to space-indent, and then apply the patch
here: indent with 4 spaces
sed -i 's/\t/ /g' setup.cfg
# or
expand -i -t4 setup.cfg | sponge setup.cfg
egg_info
usesconfigparser.ConfigParser.write
from Python's standard library, to store build-time information in thesetup.cfg
file (e.g. version parts liketag_build
andtag_date
).
so setup.cfg is modified... otherwise we could just copy the original setup.cfg
ideally, such dynamic fields should be spaced by 3 empty lines from other fields to avoid "hunk FAILED" errors when patching
without support for preserving indentation implemented in
configparser
it is not viable for setuptools to support this feature.
i guess we will wait 1000 years until configparser can do that : P
this would require a concrete syntax tree (CST) parser like tree-sitter to produce a minimal diff when editing the file
tree-sitter-ini fails to parse setup.cfg
files
because multi-line values are not supported
git clone --depth=1 https://github.com/justinmk/tree-sitter-ini
cd tree-sitter-ini
tree-sitter generate
wget https://github.com/django/django/raw/main/setup.cfg
tree-sitter parse setup.cfg | grep ERROR | wc -l
# 2
tree-sitter-toml (online) fails to parse setup.cfg
files
because in toml, string values must be quoted
tree-sitter-yaml (online) fails to parse setup.cfg
files
because yaml has no sections like [metadata]
because yaml has :
separator instead of =
etc...
I wasted time with this problem when trying to create a Conda patch, although I'm using the absolutely uniquely named and searchable "build" Python module. I tried to open an issue there but was redirected here.
The whitespace handling is documented behavior and therefore unlikely to change, imho. Although I hope that configparser (or setuptools) at least might be convinced to not produce trailing whitespaces.
I think the easiest workaround that doe not require waiting for ConfigParser to change behavior would be this workflow:
diff -w
or an equivalent Python library.There is a similar issue in bumpversion
You might wish to look into migrating to pyproject.toml
-based configuration, which wasn't available at the time when this issue was opened and which setuptools won't rewrite.
speaking of workarounds
Generate new setup.cfg with ConfigParser
use ConfigParser
to patch setup.cfg
aka syntax-aware patching
import configparser
cfg = configparser.ConfigParser()
assert cfg.read("setup.cfg") == ["setup.cfg"]
# set value
try:
cfg.add_section("metadata")
except configparser.DuplicateSectionError:
pass
cfg.set("metadata", "name", "some_name")
# remove value
try:
cfg.remove_option("some_section", "some_key")
except configparser.NoSectionError:
pass
# remove section
cfg.remove_section("some_section")
with open("setup.cfg", "w") as f:
cfg.write(f)
this could be shorter with a command line tool
pycfg-patch setup.cfg --set metadata.name=some_name
You might wish to look into migrating to
pyproject.toml
-based configuration, which wasn't available at the time when this issue was opened and which setuptools won't rewrite.
Thanks for the pointer. That might work for me as a workaround.
It seems that support was added in setuptools 61, which was released 2022-03-24. This makes me a bit hesistant regarding backwards compatibility, but I'm already using pyproject.toml build, which seems to be supported since pip 10.0 released 2018-04-14, so I should still have quite a bit of compatibility after simply increasing the required setuptools version inside the pyproject.toml.
setuptools version
65.1.1
Python version
3.10.8
OS
Arch Linux
Additional environment information
I have noticed this issue several times already when trying to apply patches for Arch Linux packages, that I supplied to upstream myself (e.g. for the mailman ecosystem). I had to basically write the patches all over again or switch to git sources to be able to apply them, which means a lot of unnecessary overhead (or may not be possible if we are relying on PGP validated source tarballs).
Description
Using
python setup.py sdist
, setuptools unconditionally adds tabs to setup.cfg and setup.py in the sdist tarball.From a downstream perspective, this is extremely tedious behavior, as it requires to adapt/backport all patches that touch those files (e.g. version updates or other data in those files).
Expected behavior
Setuptools does not touch the indentation of setup.cfg and setup.py (or any other file for that matter) when creating sdist tarballs.
How to Reproduce
git clone https://github.com/pycontribs/selinux
cd selinux
python setup.py sdist
tar -zxvf dist/selinux-*.tar.gz selinux-*/setup.cfg
(you'll have to choose a more specific dir in the 2nd argument!)diff -ruN selinux-*/setup.cfg setup.cfg
Output