> Setuptools has recently integrated a complete copy of distutils and is no longer dependent on the standard library [3]. Pip has been silently replacing distutils with setuptools when installing packages for a long time already, and the distutils documentation has stated that it is being phased out since 2014 (or earlier). It is time to remove it from the standard library.
Oh, okay. My package already requires setuptools for setup(), though it then uses distutils for the rest.
> The landscape of Python packaging is shifting and Setuptools has evolved to only provide backend support, no longer being the de-facto packaging tool in the market. All python package must provide a pyproject.toml and specify the backend (build system) it wants to use.
Wait, what?
What's a "pyproject.toml"?
My current setup.py's "build" step generates 10MB of auto-generated C extension code. Am I supposed to ship that too? Or, where do I put that hook?
I have a custom build_ext subclass so I can pass per-file compilation flags to the C compiler. How's that handled? I can't even figure out the new way to specify C extension packages in the setuptools documentation?!
And it appears that "build" in this modern era means "build a release package", and nothing like what "setup.py build", so keyword searches are failing me.
(For example, "A simple, correct PEP 517 package builder" doesn't actually build C extensions.)
AIUI, as the official way setuptools is used is a setup.py calling setuptools.setup(), doing this will remain supported in perpetuity and will really still be powering everything under the hood, it's just no longer what people are told to do when they ask 'how do I make a new python library?' - newcomers and people with simple requirements will be directed to poetry or whatever and avoid that whole mess. For obvious historical reasons python's pretty averse to large breaking changes, and its support for all kinds of weird native interops and obscure platforms is one of its major assets (but also why setuptools is such a mess), so that's almost certainly not going anywhere, even if it's now kept even further back in the shadows. Regarding pyproject.toml if you already have a setup.py doing what you need then it's really only for specifying suported python versions and setup_requires (solving the problem of your setup.py itself needing Cython/wheel/etc).
What throws me for a loop is that support for C extensions isn't documented in https://setuptools.readthedocs.io/en/latest/ , nor is there any mention of how to do something equivalent to the current "python setup.py build", so I can't tell what is "official" regarding continued setuptools support for my current use of distutils.
Here's how my setup.py starts:
from setuptools import setup
from distutils.core import Extension
from distutils.command.build_ext import build_ext
I use Extension() to define the C extension.
I use build_ext to create a subclass which figures out the appropriate OpenMP compile and link flags, which depend on the compiler being used.
I also use that build_ext subclass to handle code generation. This Python code replaced a horribly complex set of preprocessor #defines and made it much easier to debug and profile the code.
Oh, and macOS's default C compiler doesn't support OpenMP so I added support for disabling the OpenMP flags, for example, with:
CHEMFP_OPENMP=0 pip install chemfp-3.5.1.tar.gz
I looked at poetry. It says "only pure python wheels are supported", so that's not (yet?) a replacement for distutils, yes?
And it looks like pyproject.toml is supposed to replace MANIFEST/MANIFEST.in ?
Lastly, I have a commercial product. I prefer to distribute source code to my customers. I grudgingly provide pre-compiled wheels for Linux, which people use for evaluation. My main source repository has the license check code ("src/chemfp_lm.h"), which is used to make the wheels. The MANIFEST.in excludes src/chemfp_lm.h during the sdist that goes to my paying customers. To handle source compilation for both cases, setup.py changes the Extension() configuration depending on if src/chemfp_lm.h is present.
This was simple using distutils. I have no clue how to replace distutils in the first place, much less be able to handle my special case in this future Python world.
Or as you say, "all kinds of weird native interops".
FWIW, my code still supports 2.7 and 3.6+, and has no required dependencies on other Python packages, so I didn't realize there was a problem. ;)
To be clear, I have optional dependencies on "zstandard" (in PyPI), on three computational chemistry packages with Python APIs but which aren't available through PyPI, and on one Java computational chemistry JAR file, connected via jpype (in PyPI) and itself with an optional dependency on a ZStandard JAR file.
Which means pip-resolvable dependencies hasn't been something I've had to care about over the last 11 years of development.
If you're building anything past a hello world c ext you're already off the path of shit like poetry, in which case AIUI we're basically on our own as we've always been lol, for better and worse.
Again I don't think setup.py is going away. Given that distsetuputiltools is a jenga tower of decades old monkeypatches documentation is understandably sparse, but they're at least passively open about how gross it is to work within. Subclassing or otherwise manually overriding the behavior of distutils classes like build_* is just how stuff is done at this level and I don't expect that to change, beyond just updating your stuff to subclass or hook direct setuptools equivs of existing distutils classes. distutils is already vendored by setuptools anyway, so you're already not really hitting 'distutils' as much as 'setuptools._distutils' aliased to distutils upon importing setuptools (and specifically importing it first ( https://github.com/pypa/setuptools/blob/c121d289da5d19cf6df2... )), so all this really does is collapse a level of hack.
As long as py2 remains supported I expect whatever changes happen here to be py2 compat, but I do expect new setuptools to break compat with old setup.py's. But setup.py being what it is it wouldn't be out of character to make your setup.py manually support both old pre-distutils-removal setuptools and new post-distutils-removal setuptools, but that's just how setup.py maintenance goes lol.
ed: ok well not so much py2 being 'supported' as 'no showstopping incompatibilities for anyone still straddling that ever widening gap' :)
To reuse a phrase from the native-Spanish-speaking Americans of the Southwest, whose family moved there in the 1700s or earlier, "I didn't cross the border, the border crossed me."
In the late 1990s, when I started with Python, writing Python C extensions was definitely not off the beaten path. I remember reading the design discussion, and I think I contributed some input too. I probably knew the original developers from going to Python conferences.
I know that at 20+ years old it's also long in the tooth. There's certainly features I would like that just aren't going to happen with it, like being able to emit a Makefile that I can use during development for incremental rebuilds, instead of taking 10 seconds to compile.
And distutils isn't going away for another couple of years, so I can plan time to migrate.
But I don't have an inkling of what that migration path might be, and can find no mention that the current set of developers are even aware of this issue. (Not that I've looked for more than a few hours.)
> distutils isn't going away for another couple of years, so I can plan time to migrate
I'd say it's not going away for as long as setuptools will be based on it.
> BTW, is "setup.cfg" dead and forgotten now?
Basically, the same answer as above: setup.cfg was (as I understand) an internal solution to declarative build configuration (originally introduced in now-discontinued distutils2). It is basically precursor to pyproject.toml, and it was actually one of the alternatives proposed in PEP 518 [0] (but was declined because it depends on Python's configparser, making it not much more universal than setup.py).
The setuptools documentation says: "All python package must provide a pyproject.toml and specify the backend (build system) it wants to use."
What is the build system which indicates the heritage build system?
Oh, I see. That documentation conflicts with PEP 517, which states:
> If the pyproject.toml file is absent, or the build-backend key is missing, the source tree is not using this specification, and tools should revert to the legacy behaviour of running setup.py (either directly, or by implicitly invoking the setuptools.build_meta:__legacy__ backend).
I find this whole thing frustrating. Eg, the setuptools documentation mentions 'setuptools.Extension' in passing, while talking about Cython, but does not document how Extension() is used??
So far I haven't found a PEP 517-compatible build system which handles C-extensions. The closest is "bento", mentioned in the PEP, but it hasn't been updated since August 2014.
> but the purpose of PEP 518 was to come up with a way for projects to specify what build tools they required ... Now a tool like pip can read pyproject.toml, see what build tools are specified in it, and install those in a virtual environment to build your project
Except, umm, I can't do that? Because I depend on a C compiler, and have optional dependencies on Python packages which aren't in PyPI.
And I didn't see anything there about C extensions.
I feel like the forgotten child. :(
> editable installs
Grr, now I have to figure out what an "editable install" means.
EDIT: Oh, I see, I think. I work in the top-level directory with the project chemfp/ underneath and symlink to build/lib/_chemfp.so for the C extension, so I've not needed this.
You are right, building native extensions seems to have been left behind with the whole move to configuration-based builds (using pyproject.toml, as opposed to the "script-based" builds, using setup.py). That said, the configuration format itself is supposed to be universal -- but apparently none of the current tools support building extensions.
That means that you are stuck with distutils, which should be available as part of setuptools -- PEP632 offers some migration advice [0]. I'd also recommend asking for advice on the packaging discussion board [1].
EDIT: And there is also the PyPA's official packaging problems tracker, FWIW [2].
But do you specify it in the Python build toolchain, e.g. setup.py? If I'm not mistaken, the binary compilation step happens independently, with the Python build packing everything up in a wheel.
> and have optional dependencies on Python packages which aren't in PyPI.
Such dependencies can still be declared in pyproject.toml, depending on the build tool - Poetry can add them by e.g. a local path, or git repo URL. I'm not sure what happens if you depend on them for the build itself, but it should be possible to add them to the build-system table in the TOML file.
But pyproject.toml doesn't change anything compared to the current system; for backward compatibility, PEP 518 assumes this default file if a custom one is missing:
[build-system]
requires = ["setuptools", "wheel"]
Which is enough to execute the standard setup.py.
In any case, as per PEP 517, you can define your own custom packaging backend using the build-backend value in the [build-system] table.
14 comments
[ 3.2 ms ] story [ 41.3 ms ] threadWait, what?
https://www.python.org/dev/peps/pep-0632/ says:
> Setuptools has recently integrated a complete copy of distutils and is no longer dependent on the standard library [3]. Pip has been silently replacing distutils with setuptools when installing packages for a long time already, and the distutils documentation has stated that it is being phased out since 2014 (or earlier). It is time to remove it from the standard library.
Oh, okay. My package already requires setuptools for setup(), though it then uses distutils for the rest.
https://setuptools.readthedocs.io/en/latest/userguide/quicks... says:
> The landscape of Python packaging is shifting and Setuptools has evolved to only provide backend support, no longer being the de-facto packaging tool in the market. All python package must provide a pyproject.toml and specify the backend (build system) it wants to use.
Wait, what?
What's a "pyproject.toml"?
My current setup.py's "build" step generates 10MB of auto-generated C extension code. Am I supposed to ship that too? Or, where do I put that hook?
I have a custom build_ext subclass so I can pass per-file compilation flags to the C compiler. How's that handled? I can't even figure out the new way to specify C extension packages in the setuptools documentation?!
And it appears that "build" in this modern era means "build a release package", and nothing like what "setup.py build", so keyword searches are failing me.
(For example, "A simple, correct PEP 517 package builder" doesn't actually build C extensions.)
https://setuptools.readthedocs.io/en/latest/userguide/quicks... goes on to say:
> To learn more about Python packaging in general, navigate to the bottom of this page.
but that link doesn't exist!
Help?
What throws me for a loop is that support for C extensions isn't documented in https://setuptools.readthedocs.io/en/latest/ , nor is there any mention of how to do something equivalent to the current "python setup.py build", so I can't tell what is "official" regarding continued setuptools support for my current use of distutils.
Here's how my setup.py starts:
I use Extension() to define the C extension.I use build_ext to create a subclass which figures out the appropriate OpenMP compile and link flags, which depend on the compiler being used.
I also use that build_ext subclass to handle code generation. This Python code replaced a horribly complex set of preprocessor #defines and made it much easier to debug and profile the code.
Oh, and macOS's default C compiler doesn't support OpenMP so I added support for disabling the OpenMP flags, for example, with:
I looked at poetry. It says "only pure python wheels are supported", so that's not (yet?) a replacement for distutils, yes?And it looks like pyproject.toml is supposed to replace MANIFEST/MANIFEST.in ?
Lastly, I have a commercial product. I prefer to distribute source code to my customers. I grudgingly provide pre-compiled wheels for Linux, which people use for evaluation. My main source repository has the license check code ("src/chemfp_lm.h"), which is used to make the wheels. The MANIFEST.in excludes src/chemfp_lm.h during the sdist that goes to my paying customers. To handle source compilation for both cases, setup.py changes the Extension() configuration depending on if src/chemfp_lm.h is present.
This was simple using distutils. I have no clue how to replace distutils in the first place, much less be able to handle my special case in this future Python world.
Or as you say, "all kinds of weird native interops".
FWIW, my code still supports 2.7 and 3.6+, and has no required dependencies on other Python packages, so I didn't realize there was a problem. ;)
To be clear, I have optional dependencies on "zstandard" (in PyPI), on three computational chemistry packages with Python APIs but which aren't available through PyPI, and on one Java computational chemistry JAR file, connected via jpype (in PyPI) and itself with an optional dependency on a ZStandard JAR file.
Which means pip-resolvable dependencies hasn't been something I've had to care about over the last 11 years of development.
Again I don't think setup.py is going away. Given that distsetuputiltools is a jenga tower of decades old monkeypatches documentation is understandably sparse, but they're at least passively open about how gross it is to work within. Subclassing or otherwise manually overriding the behavior of distutils classes like build_* is just how stuff is done at this level and I don't expect that to change, beyond just updating your stuff to subclass or hook direct setuptools equivs of existing distutils classes. distutils is already vendored by setuptools anyway, so you're already not really hitting 'distutils' as much as 'setuptools._distutils' aliased to distutils upon importing setuptools (and specifically importing it first ( https://github.com/pypa/setuptools/blob/c121d289da5d19cf6df2... )), so all this really does is collapse a level of hack.
As long as py2 remains supported I expect whatever changes happen here to be py2 compat, but I do expect new setuptools to break compat with old setup.py's. But setup.py being what it is it wouldn't be out of character to make your setup.py manually support both old pre-distutils-removal setuptools and new post-distutils-removal setuptools, but that's just how setup.py maintenance goes lol.
ed: ok well not so much py2 being 'supported' as 'no showstopping incompatibilities for anyone still straddling that ever widening gap' :)
To reuse a phrase from the native-Spanish-speaking Americans of the Southwest, whose family moved there in the 1700s or earlier, "I didn't cross the border, the border crossed me."
In the late 1990s, when I started with Python, writing Python C extensions was definitely not off the beaten path. I remember reading the design discussion, and I think I contributed some input too. I probably knew the original developers from going to Python conferences.
I know that at 20+ years old it's also long in the tooth. There's certainly features I would like that just aren't going to happen with it, like being able to emit a Makefile that I can use during development for incremental rebuilds, instead of taking 10 seconds to compile.
And distutils isn't going away for another couple of years, so I can plan time to migrate.
But I don't have an inkling of what that migration path might be, and can find no mention that the current set of developers are even aware of this issue. (Not that I've looked for more than a few hours.)
BTW, is "setup.cfg" dead and forgotten now?
I'd say it's not going away for as long as setuptools will be based on it.
> BTW, is "setup.cfg" dead and forgotten now?
Basically, the same answer as above: setup.cfg was (as I understand) an internal solution to declarative build configuration (originally introduced in now-discontinued distutils2). It is basically precursor to pyproject.toml, and it was actually one of the alternatives proposed in PEP 518 [0] (but was declined because it depends on Python's configparser, making it not much more universal than setup.py).
[0] https://www.python.org/dev/peps/pep-0518/
What is the build system which indicates the heritage build system?
Oh, I see. That documentation conflicts with PEP 517, which states:
> If the pyproject.toml file is absent, or the build-backend key is missing, the source tree is not using this specification, and tools should revert to the legacy behaviour of running setup.py (either directly, or by implicitly invoking the setuptools.build_meta:__legacy__ backend).
I find this whole thing frustrating. Eg, the setuptools documentation mentions 'setuptools.Extension' in passing, while talking about Cython, but does not document how Extension() is used??
Regarding setup.cfg, https://setuptools.readthedocs.io/en/latest/userguide/declar... seems to say that there is still a setup.cfg but it "has limited compatibility with the distutils2-like setup.cfg sections used by the pbr and d2to1 packages".
So far I haven't found a PEP 517-compatible build system which handles C-extensions. The closest is "bento", mentioned in the PEP, but it hasn't been updated since August 2014.
Again, I suggest you to try asking for advice on the Python discussion boards and the packaging problems tracker...
https://snarky.ca/what-the-heck-is-pyproject-toml/
Quoting it:
> but the purpose of PEP 518 was to come up with a way for projects to specify what build tools they required ... Now a tool like pip can read pyproject.toml, see what build tools are specified in it, and install those in a virtual environment to build your project
Except, umm, I can't do that? Because I depend on a C compiler, and have optional dependencies on Python packages which aren't in PyPI.
And I didn't see anything there about C extensions.
I feel like the forgotten child. :(
> editable installs
Grr, now I have to figure out what an "editable install" means.
EDIT: Oh, I see, I think. I work in the top-level directory with the project chemfp/ underneath and symlink to build/lib/_chemfp.so for the C extension, so I've not needed this.
That means that you are stuck with distutils, which should be available as part of setuptools -- PEP632 offers some migration advice [0]. I'd also recommend asking for advice on the packaging discussion board [1].
EDIT: And there is also the PyPA's official packaging problems tracker, FWIW [2].
[0] https://www.python.org/dev/peps/pep-0632/#migration-advice
[1] https://discuss.python.org/c/packaging/14
[2] https://github.com/pypa/packaging-problems
But do you specify it in the Python build toolchain, e.g. setup.py? If I'm not mistaken, the binary compilation step happens independently, with the Python build packing everything up in a wheel.
> and have optional dependencies on Python packages which aren't in PyPI.
Such dependencies can still be declared in pyproject.toml, depending on the build tool - Poetry can add them by e.g. a local path, or git repo URL. I'm not sure what happens if you depend on them for the build itself, but it should be possible to add them to the build-system table in the TOML file.
But pyproject.toml doesn't change anything compared to the current system; for backward compatibility, PEP 518 assumes this default file if a custom one is missing:
Which is enough to execute the standard setup.py.In any case, as per PEP 517, you can define your own custom packaging backend using the build-backend value in the [build-system] table.