You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
more reliable and speedier builds, especially because setuptools is effectively impossible to really parallelize whereas meson is extremely parallelizable (from @eli-schwartz)
At a fundamental level, I'm all for exploring other build backends, mostly because error messages from setuptools tend to be hard to decipher, and because I'm inclined to think that more modern alternatives are better at doing one thing and do it well (setuptools predate the notion of a build backend).
I do want to note however that meson isn't the only possible choice there; scikit_build.core also comes to mind. I don't expect migration to either to be trivial1, so I think we should carefully weight the costs and benefits for more than one option.
In the mean time, we currently are tightly bound to setuptools because of our other build-time requirements. Namely setuptools-scm and extension-helpers. Whatever we do, we need to figure out how to replace or drop them too. I suspect extension-helpers might be completely unneeded if we have a proper build system like meson, but it might still make migration more painful that we don't use vanilla setuptools.
Footnotes
because I couldn't figure out how to migrate from setuptools to meson for a package much simpler than astropy, but maybe documentation has grown since I last tried ? ↩
Reacted by P. L. Lim, Nathaniel Starkman and Nick Murphy
Let me add a cautious 👍 to this idea - positive about it because I'd be happy to only have to know one mechanism, but cautious mostly because this is not little work (e.g., all setup_package.py have to be replaced by meson build files, and those are not that much more transparent about what they do) and has the disadvantage of "yet another tool".
In the mean time, we currently are tightly bound to setuptools because of our other build-time requirements. Namely setuptools-scm and extension-helpers.
Some projects I know of, use this preamble for meson:
It works fine because setuptools_scm can be run as a shell program and prints the version to stdout, so that meson knows what it is and can add it as a dynamic metadata field.
I suspect extension-helpers might be completely unneeded if we have a proper build system like meson, but it might still make migration more painful that we don't use vanilla setuptools.
If I understand correctly, extension-helpers is basically a setuptools plugin allowing you to define C-API extensions alongside the extension code itself. Similar to how numpy.distutils extended setuptools with the notion of subpackages.
Meson natively handles this, because you can do subdir() from the root meson.build file and parse a per-directory meson.build file containing paths relative to the current file to define additional extensions. After parsing all files it creates a unified build graph with a single build.ninja for parallelism, but the compiled artifacts it creates are all within your subdirectory hierarchy.
Another aspect that should be considered is how to evolve our recommendations. I'm thinking of OpenAstronomy's cookie cutter template package, which unconditionally includes setuptools as a build backend. Come to think of it I'm not exactly sure that's "our" problem to deal with, as the template lives in a different organization.
That said, we should also discuss wether we want to preserve relative uniformity in astropy coordinated packages. Should we start recommend hatchling + hatch-vcs for pure-Python packages, and meson (or whatever else we settle for in astropy) for packages with compiled extensions ?
I think uniformity for coordinated and to some extent also affiliated packages is quite important, as is consistency with the openastronomy template -- I think that involves having at least a migration guide, but ideally a migration script.
That also means one should be really sure before switching. While I like how spin works when developing numpy, I don't really see how the advantages outweigh the rather large amount of work, especially since it is just not work for us.
I think that involves having at least a migration guide, but ideally a migration script.
Migrating via a script might be feasible for pure python packages but even there I'm not sure it's worth coming up with a general enough solution. I would rather volonteer to perform the migration manually there (and predictably regret that I didn't automate the process half way through).
While I like how spin works when developing numpy, I don't really see how the advantages outweigh the rather large amount of work, especially since it is just not work for us.
I think we need to separate concerns here; if meson is needed to enable spin-based workflows, then I'd strongly oppose to this migration process because pure Python packages should really not need anything more complicated than hatchling as a build backend. In particular they shouldn't need any dynamism (with a possible exception for the version number) in setting up their metadata.
I'm not opposed to leveraging spin in general, but to me it shouldn't be our primary motivation to switch build backends, especially if it only works with meson (which I don't know if it does !).
I'm quite surprised to see hatchling bright as an example here. Hatchling is an extraordinarily complicated build backend packed full of large amounts of dynamism.
If you want:
because pure Python packages should really not need anything more complicated than XXXX
then IMO "XXXX" should be flit_core..
That being said, while pure python packages don't really need meson's power and flexibility, usually, it is certainly possible to use it for the sake of consistency, and the equivalent of flit_core in meson.build is a one-line project preamble, followed by a one-line directive for installing the *.py code.
You don't need any dynamic metadata at all, but meson-python supports setting the following fields dynamically from meson.build:
version
license
license-files
because meson supports those directly for all projects including C/C++/Fortran/Rust/Vala/Java/Swift projects that don't have any python in them at all. ;)
I'm not opposed to leveraging spin in general, but to me it shouldn't be our primary motivation to switch build backends, especially if it only works with meson (which I don't know if it does !).
Spin allows overriding builtin commands, and comes with built-in commands for meson since it was originally created by people who were using meson and wanted good developer tooling. They've said they'll accept feature requests to implement support for particular build backends that people ask for. (On the other hand, people that want "powerful developer tooling" seem to be a self-selecting group that also want a powerful build backend and are usually migrating away from setuptools because they feel like they've reached their limits and need something like meson, so that might explain a bit why nobody has opened a ticket asking for flit_core support.)
It also does come with a builtin command for "install using pip" which I guess is fully backend-agnostic and at least for flit_core probably does everything you need anyway.
Keep in mind that such refactoring would affect anyone downstream who has to build astropy from source for whatever architecture that they have to support.
I'm quite surprised to see hatchling bright as an example here. Hatchling is an extraordinarily complicated build backend packed full of large amounts of dynamism.
If you want:
because pure Python packages should really not need anything more complicated than XXXX
then IMO "XXXX" should be flit_core..
I did say "not anything more complicated than ...", and I threw hatchling there because it has a clear replacement for setuptools-scm in hatch-vcs, but if we didn't have to carry this additional layer of complexity I would have set the limit at flit-core indeed: I think it's one truly minimal build backend for pure python packages.
Hatchling is indeed very dynamic in itself, but it can be used with purely static metadata and configuration, which is what I care most about here.
Any way, if there's a known solution to support dynamic version metadata with flit-core, I'm all defaulting to it instead !
Keep in mind that such refactoring would affect anyone downstream who has to build astropy from source for whatever architecture that they have to support.
Both hatchling and flit-core are respectively top 3 and top 4 in popularity at the moment, and are both written in pure Python, so I'm confident that they are both well supported by our distributors.
Written by somebody who got tired of asking flit's developer to allow it, and realized that you can just run immediately before flit instead by making an extremely simple wrapper proxy.
I just gave it another shot and for what it's worth, I never could figure out how to make editable installs work on my system (a very common MacOS machine) for seemingly any meson-backed package (numpy comes to mind). Is macOS somehow considered exotic in this ecosystem or do I need to fix my computer first and complain later ? 😢
I'm in favor of switching to meson (but also happy to consider other build systems - as long as we can get parallel builds). However I think it might make sense to try it out first in a package like reproject which already mixes Cython and C extensions and uses extension-helpers currently, but might be a bit less overwhelming to try than astropy core! I'm not volunteering though - I tried to figure it out before and didn't manage, but I'm sure it's possible.
I'm not volunteering though - I tried to figure it out before and didn't manage, but I'm sure it's possible.
I'm slowly but but surely leveling up my meson-fu. I'll report back once I get confident enough that I can try to migrate an intermediate-size package like reproject. I also need to study scikit-build-core a bit so I can make an honest comparison between the two.
3 days in the making, here's my exploratory report
There are two asks in this issue:
leverage modern developer tooling (spin)
leverage modern build systems
It is important to note from the get-go that these concerns are separable: while there are some wins to be had in combining both, we can also choose to have one and not the other. I'll also split the build backend discussion into 2 parts:
packages with native extensions (this is relevant for astropy itself)
pure Python packages
Spin
Spin is a command runner for Python developers, like toxor nox, developed for the scientific Python ecosystem.
how it relates to meson
The inception of spin is connected to the original migration to meson as build system in the numpy and scipy communities. As such, it integrates especially well with meson/meson-python out of the box: a couple meson-oriented builtin commands are provided. Nonetheless, spin supports defining our own commands (that, I imagine, can later be polished, and upstreamed), so it seems to me that it can actually integrate with any build backend with enough work.
For this reason, I'd like to keep this part of the discussion completely separated from the choice of a build backend. I still gave it a try while I was studying build backends to ensure I understood the tool well enough to say this confidently, so I'll report my observations and conclusions hereafter.
What's relevant for the following is that meson-python has an advantage over alternative build backends if we want to later setup spin-based developer workflows.
my first-time user experience feedback
I tried to use spin for a couple minutes as a system-level tool and concluded it didn't supported this use case. As far as I can tell it is only designed to work as part of one's (virtual) development environment. Personally, I'd like a command runner to set itself at least one level lower than the commands it runs on my stack, which spin doesn't appear to be. For what it's worth, I have the exact same issue with tox and nox. In these conditions, I prefer not to use a command runner at all. A command runner I might want to use would have to be something that integrates with the tool chain at a lower level. uvmay get there at some point, but I'm not holding my breath.
Perhaps more importantly, I immediately ran into problems with all builtin commands I tried to run (spin.cmds.pip.install doesn't play nice with build isolation, spin.cmds.meson.build seems to do more than advertised, spin.cmds.meson.test raised a FileNotFoundError ...), so I'm overall not sold on the benefit of using this tool.
conclusions
In short
-0.1 for combining spin + tox or spin + nox; I'm skeptical would bring tangible benefit
+0 fort using justspin instead of tox: as long as I don't have to go out of my way to ignore them and avoid installing them, I don't have strong opinions.
-0.9 for using meson-python as a build package in every coordinated package (including ones where it's a clear overkill) if enabling spin is the sole (or primary) motivation; as we saw, spin doesn't actually requiremeson.
My personal preferences should obviously not prevent other devs from using their preferred tools, but this part of the discussion would probably be best championed by someone else (@nstarman ?). Please do include me in the conversation though :)
Build backends & build systems
migrating astropy away from setuptools + extension-helpers
Here, I give a rough requirement list of what needs to be done if we elect a new build system.
migrate all setup_package.py files to meson.build or CMakeLists.txt:
extension_helpers.get_extensions should be trivial to migrate in most cases (it's essentially just a list of source files). Cases that involve dynamic file selection are still doable.
extension_helpers.pkg_config should be easy to re-implement with a real build system
code-gen in wcs (using extension_helpers.import_file to extract a docstring from .py and then write to C files) might not be trivial to re-implement as-is, but it should be possible to re-wire the logic here to avoid bootstrapping issues (for instance, move the source of the docstring to a txt file).
(?) re-implement astropy.compiler_version.compiler (maybe not needed ? it's not documented or tested, it's just a by-product of using extension-helpers)
(?) ASTROPY_USE_SYSTEM_* switches could be re-implemented via meson.options or scikit-build-core's override system. I haven't found ways to implement options via env vars (which I assume is deliberate), so it would technically be a breaking change, though, one that only affects developers (and maybe some distributors).
Choosing a build backend
Packages with extensions
I've explored our two main options to go beyond setuptools: meson-python and scikit-build-core.
disclaimer: in the process of studying them, I gained first-hand experience with meson-python, but only studiedscikit-build-core (meaning, I read the relevant parts of its documentation), so my opinion may be biased in favor of meson-python.
Both options have a lot in common, so, before I list differentiating features, I want to give a general overview of what migrating away from setuptools means:
a better build experience:
parallelism
true editable mode (even works for compiled code)
reduced warning noise (warning free builds)
improved distribution artifacts
more optimised (and sometimes smaller) binary extensions by default (setuptools exports some compilation flags from sysconfig by default, like -fno-strict-overflow, which disables some optimisation passes on the compiler)
we get to ditch legacy metadata formats like egg-info for free
require an actual build system, which is a system-level build-time dependency. I want to stress that while leveraging a real build system has clear benefits, it's also a significant change in our DX, which will no doubt lead to confusion for contributors at large. We do already require a C compiler (and pkg-config), though it is so common to have one installed already that we rarely ever see a contributor struggling with this. Build systems might be significantly less common, so we need to make sure the core team understands this aspect well. In particular, this will affect developer workflows around editable installs. Nothing unsurmountable, but again, we need to collectively understand this well. The take-home message is that combining build isolation + editable mode isn't possible.
brings astropy in line with its "spiritual parent" projects (numpy, scipy, matplotlib ...)
(unique) cons
documentation is dense and maybe not ideally structured: I couldn't figure out the basic stuff just by looking at existing examples, and I noticed that most docs pages assume you already know a lot, so it's a bit difficult to get a good foundation without guidance.
neutral / note worthy
Py_LIMITED_API (abi3 portable wheels) support makes it trivial to build portable (if slightly less optimized) wheels
builds respect .gitattribute, which could be a more portable way to declare exclusions from published artifacts, though I do not currently know of any other build backend that does this
I found that DX was overall positive with uv, though it's still short of ideal (it might get better though)
syntax highlighting for meson.build is trivial to get in vscode (install the meson extension)
unknown
how easy would it be to migrate distribution on conda-forge ?
Although meson-python (and probably scikit-build-core) can be used for pure-Python projects, I don't think that the marginal gain of having a single toolchain in place for all affiliated packages is worth the cost of bringing these huge tool chains along where much simpler solutions exist.
There are 3 well maintained, and very popular, build backends for pure Python projects at the time of writing, all three of which are written in Python themselves, which is ideal for distributors, as discussed in astral-sh/uv#3957
setuptools
pros
This is the status quo: if it's not going anywhere, so maybe we don't want to change it ?
Half of all projects on PyPI that declare a build backend explicitly still use it at the time of writing, and it's the backend pip defaults to if none is explicitly set.
cons
convoluted: as the mother of all build backend, setuptools is very hard to maintain and evolve. It might freeze into a bug-to-bug compatibility policy.
Error messages are not always clear; I've maintained many setuptools-based packages for years and I still don't always get what's going on when a build fails.
doesn't have an associated build frontend that it integrates particularly well with (that I know of)
flit-core
This is the minimal (no dependencies) modern take on PEP 517. It has a lot less baggage than setuptools and should be sufficient for our needs. flit-scm replaces setuptools-scm.
pros
zero dependencies
version "pinning" is recommended, which is probably a good idea for a build-time requirement (makes building much more future-proof, at virtually no cost for end users)
bare bones, clean distribution artifacts by defaults (even from a dirty copy with untracked/uncommited files), which reduces the amount of configuration needed and helps with manual publishing if anyone's into that.
less minimal (has four direct dependencies, a more pluggable/extensible take. hatch-vcs replaces setuptools-scm.
pros
VCS aware: untracked files are never included in distribution artifacts
has a favorite build frontend: hatch
cons
has dependencies (but no indirect dependencies)
dirty distribution artifacts by default (any file tracked by the VCS is included): explicit selection is mandatory, in a sense.
neutral
extensibility (good to have where needed, but shouldn't be a 'pro' where it's not)
I've used all . To me, the important difference is in how much fine tuning is needed to get clean source distributions.
My personal preference (at the time of writing) is hatchling because VSC awareness makes it easier to get a portable configuration, especially because it supports an explicit-only inclusion style (only-include), and even though I have (so far) never got bitten by a broken dependency tree with hatchling, I do get the appeal of "no dependencies" that one gets with flit-core. Generally I think it's mostly a matter of taste, but in a context where our recommendation end up in dozens of packages with varying levels of long-term active support, I think we should default to flit-core; active maintainers who care enough to have an opinion here would still be free to switch whenever, while the rest of them are (even) less likely to get bitten by bit rot.
Closing recommendations
I recommend that we
migrate away from setuptools as our universal build backend, and from extension-helpers as our proxy build system.
use flit-core instead for pure Python packages
use a proper build system and the associated build backend (meson-python or scikit-build-core) for packages with native extensions (including the core library). I'm slightly inclined to prefer meson over CMake because it integrates better out of the box with spin, is used by our spiritual parents projects (numpy, scipy, matplotlib ...) and because I now have a tiny bit of field-experience with it (going great so far), while I only have a theoretical understanding of scikit-build-core. It would still make sense to favor whatever build system our community is already familiar with, if any.
change our org-level recommendations accordingly
open a separate discussion about modernizing our command-runner(s)
I'd be happy to give a go at migrating astropy itself to, e.g., meson-python. Alternatively, I could also start with a smaller package (like reproject, as proposed by @astrofrog), which might serve as further training and a proof of concept.
@neutrinoceros - thanks for the update. I must admit the case for any change seems rather weak to me, especially if it means that astropy and affiliated packages will start to diverge -- I think we shouldn't underestimate the benefits of doing things the same way in as large a group of packages as possible. Most people really do not want to have to think about build systems!
My other reason for thinking that it would be wise not to change is that it seems the state of python packaging is very much in flux. While we have a working solution, it seems far better to just wait for a clear winner, rather than jump on a current bandwagon.
We do already require a C compiler (and pkg-config), though it is so common to have one installed already that we rarely ever see a contributor struggling with this.
That is not true. As an experienced contributor, I still get a headache every time I upgrade Windows OS and have to figure out the MSVC compiler and how to get astropy env to see it, all over again. This also means that this is still a barrier for new contributors who have to build astropy from source on Windows. As far as I can understand from the notes above (thanks for all the details!), this problem will not go away no matter which new build backend we choose.
brings astropy in line with its "spiritual parent" projects
But is that a good thing? I have little desire to build the "spiritual parents" from source. I tried it once when I wanted to bisect scipy and failed (something something about Fortran). I am glad I could install their "nightly" wheels and be done with it. But if astropy becomes complicated like them, then my DX would decrease.
That is not true. As an experienced contributor, I still get a headache every time I upgrade Windows OS and have to figure out the MSVC compiler and how to get astropy env to see it, all over again. This also means that this is still a barrier for new contributors who have to build astropy from source on Windows. As far as I can understand from the notes above (thanks for all the details!), this problem will not go away no matter which new build backend we choose.
In fact, as long as you have the MSVC compiler installed, anywhere on your system, Meson solves the problem of getting the build environment to see it.
The problem here is that Microsoft, in their infinite wisdom, decided that cl.exe (the compiler) is going to depend on random undocumented environment variables that have to be set before you can use it -- even if cl.exe is in PATH, which it won't be. The only official way to set this up, is to use vswhere.exe to figure out where the relevant *.bat scripts are installed, and run one of such scripts (depending on what architecture you're building for?) and then that script will set up the right environment variables.
Using a MSVC dev command prompt does this for you.
Meson detects the state of affairs where you haven't manually added a GCC to your Windows path and aren't in such a MSVC dev command prompt yet, and says "okay, this is kinda useless, you have MSVC installed but it requires setup. Let me set it up for you, because this nonsense is a total pain". So -- it seeks out the bat script, executes it in a subprocess which then prints out the resolved environment, and reads that back in from the bat script's stdout, parses it into a key-value table of environment variables, and updates the meson process with those environment variables. Now, cl.exe can be found and it works. Everything is great. Yay!
You can force it to do this (locate a MSVC compiler) even when you have a GCC installed, by passing --vsenv to meson setup. It is ignored on non-Windows, so if you are absolutely positive that all people building wheels should be using cl.exe on Windows, you can set it in the meson default_options or via meson-python's pyproject.toml settings.
This will prevent people from compiling native Windows python extensions using mingw GCC but maybe you don't care about that since native Windows python extensions need to be compatible with the Python.org distributable cpython installer built using MSVC. It's not suitable for projects with a lot of Fortran code, since Fortran on Windows tends to mean you need GCC for gfortran and simply have to bite the bullet and be careful about compatibly gluing together GCC and the microsoft CRT, but that may not be Astropy's problem since you don't directly have any Fortran and the included wcslib appears to be compiled without the fortran wrappers since I guess you don't need them. Anyway. Point is, meson can't default to always loading "--vsenv" because it is a per-project decision whether that tradeoff makes sense.
Setuptools may be a build backend, but it is not a full-fledged build system and this is noticeable precisely because of "little" things like this.
But is that a good thing? I have little desire to build the "spiritual parents" from source. I tried it once when I wanted to bisect scipy and failed (something something about Fortran). I am glad I could install their "nightly" wheels and be done with it. But if astropy becomes complicated like them, then my DX would decrease.
Obviously most people should prefer to install wheels. Unfortunately, building software from source is complicated. Astropy contains source code for software, therefore it is already complicated.
Believe it or not :) but scipy became significantly less complicated to build from source by adopting meson. It was even worse when they were using setuptools.
Fortran is a bit of a worst-case scenario for Windows, because it's not enough to install Microsoft's official Visual Studio download and then pip install some code and twiddle your thumbs while it does stuff in the background for a while. Microsoft only provides a C/C++ compiler, no Fortran one, and the only freely licensed Windows Fortran compiler for a very long time was GCC gfortran, which means you have to deal with the dreaded ABI beast and hello complexity explosion. It is so hard to build scipy from source on Windows that their official docs are full of "words of warning" begging you not to. If you do try, your best bet is to use conda which is set up to install various dependencies you will need.
It is much better for C/C++, in the sense that nobody actually wants to do it but there are instructions you can follow to set it up and it is painful but works reliably as long as you can follow instructions.
This is true regardless of build backend, but the instructions have fewer steps if you use Meson compared to if you use setuptools, since as I said above, Meson will at least detect MSVC automatically for you once you have downloaded and run the MSVC installer at least once.
Reacted by P. L. Lim, Nathaniel Starkman and NHOrus
What I am most excited about is the potential for having the source build of astropy be parallelized - which should speed up tox, and I am also intrigued by the ability to have editable installs work for e.g. C code too, which I hadn't even realized would be possible.
I would be in favor of recommending/using flit for pure Python packages. I used it for one of my packages recently and it was super easy to get set up. Having said that, migrating all the existing packages that use setuptools to using flit is not really a high priority (I wonder if it could be partially or fully automated though).
Regarding scikit-build-core vs meson-python - am I correct in thinking that the former would require cmake to be installed separately from python, whereas meson should not require any additional system dependencies? (that is, all build requirements beyond compilers would be installable by pip, and should be handled by the build isolation). If so, that would be a strong point in favor of meson.
I don't have strong opinions regarding trying on e.g. reproject first vs just going for it and trying astropy core. To some extend, getting it to work on astropy will be proof it should work for all our other packages, but also it might be a lot of effort if we decide not to go with it.
I should also add that I'm not particularly enthusiastic about switching to spin instead of tox. While switching backend for building should in principle be pretty invisible for the average contributor, changing from tox to spin would be a bigger change, and it took a while to get to the point where most packages in the ecosystem are using tox.
Having said that, migrating all the existing packages that use setuptools to using flit is not really a high priority (I wonder if it could be partially or fully automated though).
I think this would be easy to automate if all affected packages were already using pyproject.toml for package metadata (as opposed to setup.cfg), and I don't know that it's the case. ini2toml helps a lot with migrating to pyproject.toml but manual intervention is still needed because, e.g., some tools that support configuration through setup.cfg don't actually support pyproject.toml (most notably, flake8). Anyway, I agree it's not a high priority.
Regarding scikit-build-core vs meson-python - am I correct in thinking that the former would require cmake to be installed separately from python, whereas meson should not require any additional system dependencies? (that is, all build requirements beyond compilers would be installable by pip, and should be handled by the build isolation).
kinda: with build isolation, every indirect requirements for meson-python get pulled out from PyPI (including meson itself), but, if build-isolation is disabled (which is required for editable installs), then you need to make sure that ninja is installed too. ninja is available on PyPI but can also be installed at the system level, which is the recommended way to develop with meson-python.
As far as I can tell, scikit-build-core makes no attempt at setting up CMake for you.
kinda: with build isolation, every indirect requirements for meson-python get pulled out from PyPI (including meson itself), but, if build-isolation is disabled (which is required for editable installs), then you need to make sure that ninja is installed too. ninja is available on PyPI but can also be installed at the system level, which is the recommended way to develop with meson-python. As far as I can tell, scikit-build-core makes no attempt at setting up CMake for you.
Ok so the main impact here as you said before too is that for editable installs we can no longer use build isolation. I think it might be an acceptable cost (at least from my perspective). What happens in practice if someone tries to do pip install -e .. Will it give an error? Will it be clear what the developer has to do?
Resolved 2 packages in 81ms
× Failed to build `gpgi @ file:///Users/clm/dev/gpgi`
├─▶ The build backend returned an error
╰─▶ Call to `mesonpy.build_editable` failed (exit status: 1)
[stderr]
Traceback (most recent call last):
File "<string>", line 8, in <module>
import mesonpy as backend
ModuleNotFoundError: No module named 'mesonpy'
hint: This usually indicates a problem with the package or the build environment.
As far as I can tell, scikit-build-core makes no attempt at setting up CMake for you.
It does, in the same way as meson-python will install ninja, scikit-build-core will install cmake for you from PyPI if it isn't already installed system-wide. I think meson-python still has a significant advantage here, because ninja is ~300 kb and meson 1 MB, while cmake is 30-50 MB so downloading it all the time is painful. But if you're on a fast connection, the experience from pip install is similar.
Reacted by Clément Robert, Thomas Robitaille and Nathaniel Starkman
Don't forget that some pretty funny things can happen if you try building on a system without a cmake wheel available -- it's available for Linux i686/x86-64, ppc64le, s390x, and armv7l/aarch64 only, and not all of those either if for some reason you insist on building new software on old systems with old glibc.
The same thing is true for ninja of course, which both mesin and cmake need, although I suspect ninja is more commonly installed.
Either way: you'll end up attempting to build the affected package from source too, so it really is quite good to have it preinstalled.
...
@rgommers I wonder if it would be possible for meson-python to detect build isolation and either provide a helpful error message or at least degrade the editable loader to print a warning about C/C++ not being updated then continue instead of erroring out?
So, I'm trying to see if I can get sherpa to build with meson-python rather than setuptools and it looks like I've got most major issues working in sherpa/sherpa#1948
One interesting issue was that pytest assert rewrites don't seem to fire when using editable builds, which breaks some tests (these are tests of our test code, so no functional change, but still annoying). I leave
Activity
neutrinoceros commentedon Feb 11, 2025
At a fundamental level, I'm all for exploring other build backends, mostly because error messages from setuptools tend to be hard to decipher, and because I'm inclined to think that more modern alternatives are better at doing one thing and do it well (setuptools predate the notion of a build backend).
I do want to note however that meson isn't the only possible choice there;
scikit_build.corealso comes to mind. I don't expect migration to either to be trivial1, so I think we should carefully weight the costs and benefits for more than one option.In the mean time, we currently are tightly bound to setuptools because of our other build-time requirements. Namely
setuptools-scmandextension-helpers. Whatever we do, we need to figure out how to replace or drop them too. I suspectextension-helpersmight be completely unneeded if we have a proper build system like meson, but it might still make migration more painful that we don't use vanilla setuptools.Footnotes
because I couldn't figure out how to migrate from setuptools to meson for a package much simpler than astropy, but maybe documentation has grown since I last tried ? ↩
mhvk commentedon Feb 11, 2025
Let me add a cautious 👍 to this idea - positive about it because I'd be happy to only have to know one mechanism, but cautious mostly because this is not little work (e.g., all
setup_package.pyhave to be replaced by meson build files, and those are not that much more transparent about what they do) and has the disadvantage of "yet another tool".eli-schwartz commentedon Feb 12, 2025
Some projects I know of, use this preamble for meson:
It works fine because setuptools_scm can be run as a shell program and prints the version to stdout, so that meson knows what it is and can add it as a dynamic metadata field.
If I understand correctly, extension-helpers is basically a setuptools plugin allowing you to define C-API extensions alongside the extension code itself. Similar to how numpy.distutils extended setuptools with the notion of subpackages.
Meson natively handles this, because you can do
subdir()from the root meson.build file and parse a per-directory meson.build file containing paths relative to the current file to define additional extensions. After parsing all files it creates a unified build graph with a singlebuild.ninjafor parallelism, but the compiled artifacts it creates are all within your subdirectory hierarchy.neutrinoceros commentedon Feb 12, 2025
Another aspect that should be considered is how to evolve our recommendations. I'm thinking of OpenAstronomy's cookie cutter template package, which unconditionally includes setuptools as a build backend. Come to think of it I'm not exactly sure that's "our" problem to deal with, as the template lives in a different organization.
That said, we should also discuss wether we want to preserve relative uniformity in astropy coordinated packages. Should we start recommend
hatchling+hatch-vcsfor pure-Python packages, and meson (or whatever else we settle for in astropy) for packages with compiled extensions ?mhvk commentedon Feb 12, 2025
I think uniformity for coordinated and to some extent also affiliated packages is quite important, as is consistency with the openastronomy template -- I think that involves having at least a migration guide, but ideally a migration script.
That also means one should be really sure before switching. While I like how
spinworks when developing numpy, I don't really see how the advantages outweigh the rather large amount of work, especially since it is just not work for us.neutrinoceros commentedon Feb 12, 2025
Migrating via a script might be feasible for pure python packages but even there I'm not sure it's worth coming up with a general enough solution. I would rather volonteer to perform the migration manually there (and predictably regret that I didn't automate the process half way through).
I think we need to separate concerns here; if meson is needed to enable
spin-based workflows, then I'd strongly oppose to this migration process because pure Python packages should really not need anything more complicated thanhatchlingas a build backend. In particular they shouldn't need any dynamism (with a possible exception for the version number) in setting up their metadata.I'm not opposed to leveraging spin in general, but to me it shouldn't be our primary motivation to switch build backends, especially if it only works with meson (which I don't know if it does !).
eli-schwartz commentedon Feb 12, 2025
I'm quite surprised to see hatchling bright as an example here. Hatchling is an extraordinarily complicated build backend packed full of large amounts of dynamism.
If you want:
then IMO "XXXX" should be flit_core..
That being said, while pure python packages don't really need meson's power and flexibility, usually, it is certainly possible to use it for the sake of consistency, and the equivalent of flit_core in
meson.buildis a one-line project preamble, followed by a one-line directive for installing the*.pycode.You don't need any dynamic metadata at all, but meson-python supports setting the following fields dynamically from meson.build:
because meson supports those directly for all projects including C/C++/Fortran/Rust/Vala/Java/Swift projects that don't have any python in them at all. ;)
Spin allows overriding builtin commands, and comes with built-in commands for meson since it was originally created by people who were using meson and wanted good developer tooling. They've said they'll accept feature requests to implement support for particular build backends that people ask for. (On the other hand, people that want "powerful developer tooling" seem to be a self-selecting group that also want a powerful build backend and are usually migrating away from setuptools because they feel like they've reached their limits and need something like meson, so that might explain a bit why nobody has opened a ticket asking for flit_core support.)
It also does come with a builtin command for "install using pip" which I guess is fully backend-agnostic and at least for flit_core probably does everything you need anyway.
pllim commentedon Feb 12, 2025
Keep in mind that such refactoring would affect anyone downstream who has to build astropy from source for whatever architecture that they have to support.
neutrinoceros commentedon Feb 12, 2025
I did say "not anything more complicated than ...", and I threw hatchling there because it has a clear replacement for
setuptools-scminhatch-vcs, but if we didn't have to carry this additional layer of complexity I would have set the limit atflit-coreindeed: I think it's one truly minimal build backend for pure python packages.Hatchling is indeed very dynamic in itself, but it can be used with purely static metadata and configuration, which is what I care most about here.
Any way, if there's a known solution to support dynamic version metadata with flit-core, I'm all defaulting to it instead !
Both hatchling and flit-core are respectively top 3 and top 4 in popularity at the moment, and are both written in pure Python, so I'm confident that they are both well supported by our distributors.
eli-schwartz commentedon Feb 12, 2025
Little known fact, but https://pypi.org/project/flit-scm/ exists and bridges setuptools-scm to flit-core. It's quite neat.
Written by somebody who got tired of asking flit's developer to allow it, and realized that you can just run immediately before flit instead by making an extremely simple wrapper proxy.
nstarman commentedon Feb 12, 2025
Also see https://learn.scientific-python.org/development/guides/packaging-simple/. If we are improving Astropy and OpenAstronomy's recommendations we probably want to do so within the context of the Scientific Python guidelines.
neutrinoceros commentedon Feb 12, 2025
I just gave it another shot and for what it's worth, I never could figure out how to make editable installs work on my system (a very common MacOS machine) for seemingly any meson-backed package (numpy comes to mind). Is macOS somehow considered exotic in this ecosystem or do I need to fix my computer first and complain later ? 😢
14 remaining items
astrofrog commentedon Feb 15, 2025
I'm in favor of switching to meson (but also happy to consider other build systems - as long as we can get parallel builds). However I think it might make sense to try it out first in a package like reproject which already mixes Cython and C extensions and uses extension-helpers currently, but might be a bit less overwhelming to try than astropy core! I'm not volunteering though - I tried to figure it out before and didn't manage, but I'm sure it's possible.
neutrinoceros commentedon Feb 16, 2025
I'm slowly but but surely leveling up my meson-fu. I'll report back once I get confident enough that I can try to migrate an intermediate-size package like reproject. I also need to study scikit-build-core a bit so I can make an honest comparison between the two.
neutrinoceros commentedon Feb 17, 2025
I'm now deeply engaged in this rabbit hole. Stay tuned for a detailed report on our options, sometimes this week (most likely 2 days from now).
neutrinoceros commentedon Feb 19, 2025
General introduction
3 days in the making, here's my exploratory report
There are two asks in this issue:
spin)It is important to note from the get-go that these concerns are separable: while there are some wins to be had in combining both, we can also choose to have one and not the other. I'll also split the build backend discussion into 2 parts:
Spin
Spin is a command runner for Python developers, like
toxornox, developed for the scientific Python ecosystem.how it relates to
mesonThe inception of
spinis connected to the original migration tomesonas build system in thenumpyandscipycommunities. As such, it integrates especially well withmeson/meson-pythonout of the box: a couplemeson-oriented builtin commands are provided. Nonetheless,spinsupports defining our own commands (that, I imagine, can later be polished, and upstreamed), so it seems to me that it can actually integrate with any build backend with enough work.For this reason, I'd like to keep this part of the discussion completely separated from the choice of a build backend. I still gave it a try while I was studying build backends to ensure I understood the tool well enough to say this confidently, so I'll report my observations and conclusions hereafter.
What's relevant for the following is that
meson-pythonhas an advantage over alternative build backends if we want to later setupspin-based developer workflows.my first-time user experience feedback
I tried to use
spinfor a couple minutes as a system-level tool and concluded it didn't supported this use case. As far as I can tell it is only designed to work as part of one's (virtual) development environment. Personally, I'd like a command runner to set itself at least one level lower than the commands it runs on my stack, whichspindoesn't appear to be. For what it's worth, I have the exact same issue withtoxandnox. In these conditions, I prefer not to use a command runner at all. A command runner I might want to use would have to be something that integrates with the tool chain at a lower level.uvmay get there at some point, but I'm not holding my breath.Perhaps more importantly, I immediately ran into problems with all builtin commands I tried to run (
spin.cmds.pip.installdoesn't play nice with build isolation,spin.cmds.meson.buildseems to do more than advertised,spin.cmds.meson.testraised aFileNotFoundError...), so I'm overall not sold on the benefit of using this tool.conclusions
In short
spin+toxorspin+nox; I'm skeptical would bring tangible benefitspininstead oftox: as long as I don't have to go out of my way to ignore them and avoid installing them, I don't have strong opinions.spinis the sole (or primary) motivation; as we saw,spindoesn't actually requiremeson.My personal preferences should obviously not prevent other devs from using their preferred tools, but this part of the discussion would probably be best championed by someone else (@nstarman ?). Please do include me in the conversation though :)
Build backends & build systems
migrating
astropyaway fromsetuptools+extension-helpersHere, I give a rough requirement list of what needs to be done if we elect a new build system.
setup_package.pyfiles tomeson.buildorCMakeLists.txt:extension_helpers.get_extensionsshould be trivial to migrate in most cases (it's essentially just a list of source files). Cases that involve dynamic file selection are still doable.extension_helpers.pkg_configshould be easy to re-implement with a real build systemwcs(usingextension_helpers.import_fileto extract a docstring from.pyand then write to C files) might not be trivial to re-implement as-is, but it should be possible to re-wire the logic here to avoid bootstrapping issues (for instance, move the source of the docstring to a txt file).astropy.compiler_version.compiler(maybe not needed ? it's not documented or tested, it's just a by-product of usingextension-helpers)ASTROPY_USE_SYSTEM_*switches could be re-implemented viameson.optionsorscikit-build-core's override system. I haven't found ways to implement options via env vars (which I assume is deliberate), so it would technically be a breaking change, though, one that only affects developers (and maybe some distributors).Choosing a build backend
Packages with extensions
I've explored our two main options to go beyond
setuptools:meson-pythonandscikit-build-core.Both options have a lot in common, so, before I list differentiating features, I want to give a general overview of what migrating away from
setuptoolsmeans:setuptoolsexports some compilation flags fromsysconfigby default, like-fno-strict-overflow, which disables some optimisation passes on the compiler)egg-infofor freepkg-config), though it is so common to have one installed already that we rarely ever see a contributor struggling with this. Build systems might be significantly less common, so we need to make sure the core team understands this aspect well. In particular, this will affect developer workflows around editable installs. Nothing unsurmountable, but again, we need to collectively understand this well. The take-home message is that combining build isolation + editable mode isn't possible.meson-python
(unique) pros
spinmeson.optionsnumpy,scipy,matplotlib...)(unique) cons
neutral / note worthy
.gitattribute, which could be a more portable way to declare exclusions from published artifacts, though I do not currently know of any other build backend that does thisuv, though it's still short of ideal (it might get better though)meson.buildis trivial to get in vscode (install the meson extension)unknown
scikit-build-core(unique) pros
(unique) cons
neutral / note worthy
scikit-build-core. It could probably gain support for meson-python too.Pure-Python packages
Although
meson-python(and probablyscikit-build-core) can be used for pure-Python projects, I don't think that the marginal gain of having a single toolchain in place for all affiliated packages is worth the cost of bringing these huge tool chains along where much simpler solutions exist.There are 3 well maintained, and very popular, build backends for pure Python projects at the time of writing, all three of which are written in Python themselves, which is ideal for distributors, as discussed in astral-sh/uv#3957
setuptoolspros
pipdefaults to if none is explicitly set.cons
setuptoolsis very hard to maintain and evolve. It might freeze into a bug-to-bug compatibility policy.setuptools-based packages for years and I still don't always get what's going on when a build fails.flit-coreThis is the minimal (no dependencies) modern take on PEP 517. It has a lot less baggage than
setuptoolsand should be sufficient for our needs.flit-scmreplacessetuptools-scm.pros
flitcons
flithatchlingless minimal (has four direct dependencies, a more pluggable/extensible take.
hatch-vcsreplacessetuptools-scm.pros
hatchcons
neutral
I've used all . To me, the important difference is in how much fine tuning is needed to get clean source distributions.
My personal preference (at the time of writing) is
hatchlingbecause VSC awareness makes it easier to get a portable configuration, especially because it supports an explicit-only inclusion style (only-include), and even though I have (so far) never got bitten by a broken dependency tree with hatchling, I do get the appeal of "no dependencies" that one gets with flit-core. Generally I think it's mostly a matter of taste, but in a context where our recommendation end up in dozens of packages with varying levels of long-term active support, I think we should default toflit-core; active maintainers who care enough to have an opinion here would still be free to switch whenever, while the rest of them are (even) less likely to get bitten by bit rot.Closing recommendations
I recommend that we
setuptoolsas our universal build backend, and fromextension-helpersas our proxy build system.flit-coreinstead for pure Python packagesmeson-pythonorscikit-build-core) for packages with native extensions (including the core library). I'm slightly inclined to prefermesonoverCMakebecause it integrates better out of the box withspin, is used by our spiritual parents projects (numpy,scipy,matplotlib...) and because I now have a tiny bit of field-experience with it (going great so far), while I only have a theoretical understanding ofscikit-build-core. It would still make sense to favor whatever build system our community is already familiar with, if any.I'd be happy to give a go at migrating astropy itself to, e.g.,
meson-python. Alternatively, I could also start with a smaller package (likereproject, as proposed by @astrofrog), which might serve as further training and a proof of concept.mhvk commentedon Feb 19, 2025
@neutrinoceros - thanks for the update. I must admit the case for any change seems rather weak to me, especially if it means that astropy and affiliated packages will start to diverge -- I think we shouldn't underestimate the benefits of doing things the same way in as large a group of packages as possible. Most people really do not want to have to think about build systems!
My other reason for thinking that it would be wise not to change is that it seems the state of python packaging is very much in flux. While we have a working solution, it seems far better to just wait for a clear winner, rather than jump on a current bandwagon.
pllim commentedon Feb 19, 2025
That is not true. As an experienced contributor, I still get a headache every time I upgrade Windows OS and have to figure out the MSVC compiler and how to get astropy env to see it, all over again. This also means that this is still a barrier for new contributors who have to build astropy from source on Windows. As far as I can understand from the notes above (thanks for all the details!), this problem will not go away no matter which new build backend we choose.
But is that a good thing? I have little desire to build the "spiritual parents" from source. I tried it once when I wanted to bisect scipy and failed (something something about Fortran). I am glad I could install their "nightly" wheels and be done with it. But if astropy becomes complicated like them, then my DX would decrease.
eli-schwartz commentedon Feb 19, 2025
In fact, as long as you have the MSVC compiler installed, anywhere on your system, Meson solves the problem of getting the build environment to see it.
The problem here is that Microsoft, in their infinite wisdom, decided that
cl.exe(the compiler) is going to depend on random undocumented environment variables that have to be set before you can use it -- even ifcl.exeis in PATH, which it won't be. The only official way to set this up, is to usevswhere.exeto figure out where the relevant *.bat scripts are installed, and run one of such scripts (depending on what architecture you're building for?) and then that script will set up the right environment variables.Using a MSVC dev command prompt does this for you.
Meson detects the state of affairs where you haven't manually added a GCC to your Windows path and aren't in such a MSVC dev command prompt yet, and says "okay, this is kinda useless, you have MSVC installed but it requires setup. Let me set it up for you, because this nonsense is a total pain". So -- it seeks out the bat script, executes it in a subprocess which then prints out the resolved environment, and reads that back in from the bat script's stdout, parses it into a key-value table of environment variables, and updates the meson process with those environment variables. Now,
cl.execan be found and it works. Everything is great. Yay!You can force it to do this (locate a MSVC compiler) even when you have a GCC installed, by passing
--vsenvto meson setup. It is ignored on non-Windows, so if you are absolutely positive that all people building wheels should be usingcl.exeon Windows, you can set it in the meson default_options or via meson-python's pyproject.toml settings.This will prevent people from compiling native Windows python extensions using mingw GCC but maybe you don't care about that since native Windows python extensions need to be compatible with the Python.org distributable cpython installer built using MSVC. It's not suitable for projects with a lot of Fortran code, since Fortran on Windows tends to mean you need GCC for gfortran and simply have to bite the bullet and be careful about compatibly gluing together GCC and the microsoft CRT, but that may not be Astropy's problem since you don't directly have any Fortran and the included wcslib appears to be compiled without the fortran wrappers since I guess you don't need them. Anyway. Point is, meson can't default to always loading "--vsenv" because it is a per-project decision whether that tradeoff makes sense.
Setuptools may be a build backend, but it is not a full-fledged build system and this is noticeable precisely because of "little" things like this.
Obviously most people should prefer to install wheels. Unfortunately, building software from source is complicated. Astropy contains source code for software, therefore it is already complicated.
Believe it or not :) but scipy became significantly less complicated to build from source by adopting meson. It was even worse when they were using setuptools.
Fortran is a bit of a worst-case scenario for Windows, because it's not enough to install Microsoft's official Visual Studio download and then pip install some code and twiddle your thumbs while it does stuff in the background for a while. Microsoft only provides a C/C++ compiler, no Fortran one, and the only freely licensed Windows Fortran compiler for a very long time was GCC gfortran, which means you have to deal with the dreaded ABI beast and hello complexity explosion. It is so hard to build scipy from source on Windows that their official docs are full of "words of warning" begging you not to. If you do try, your best bet is to use conda which is set up to install various dependencies you will need.
It is much better for C/C++, in the sense that nobody actually wants to do it but there are instructions you can follow to set it up and it is painful but works reliably as long as you can follow instructions.
This is true regardless of build backend, but the instructions have fewer steps if you use Meson compared to if you use setuptools, since as I said above, Meson will at least detect MSVC automatically for you once you have downloaded and run the MSVC installer at least once.
astrofrog commentedon Feb 20, 2025
@neutrinoceros thanks for the detailed report!
What I am most excited about is the potential for having the source build of astropy be parallelized - which should speed up tox, and I am also intrigued by the ability to have editable installs work for e.g. C code too, which I hadn't even realized would be possible.
I would be in favor of recommending/using flit for pure Python packages. I used it for one of my packages recently and it was super easy to get set up. Having said that, migrating all the existing packages that use setuptools to using flit is not really a high priority (I wonder if it could be partially or fully automated though).
Regarding
scikit-build-corevsmeson-python- am I correct in thinking that the former would requirecmaketo be installed separately from python, whereas meson should not require any additional system dependencies? (that is, all build requirements beyond compilers would be installable by pip, and should be handled by the build isolation). If so, that would be a strong point in favor of meson.I don't have strong opinions regarding trying on e.g. reproject first vs just going for it and trying astropy core. To some extend, getting it to work on astropy will be proof it should work for all our other packages, but also it might be a lot of effort if we decide not to go with it.
astrofrog commentedon Feb 20, 2025
I should also add that I'm not particularly enthusiastic about switching to spin instead of
tox. While switching backend for building should in principle be pretty invisible for the average contributor, changing fromtoxtospinwould be a bigger change, and it took a while to get to the point where most packages in the ecosystem are using tox.neutrinoceros commentedon Feb 20, 2025
I think this would be easy to automate if all affected packages were already using
pyproject.tomlfor package metadata (as opposed tosetup.cfg), and I don't know that it's the case.ini2tomlhelps a lot with migrating topyproject.tomlbut manual intervention is still needed because, e.g., some tools that support configuration throughsetup.cfgdon't actually supportpyproject.toml(most notably,flake8). Anyway, I agree it's not a high priority.kinda: with build isolation, every indirect requirements for
meson-pythonget pulled out fromPyPI(includingmesonitself), but, if build-isolation is disabled (which is required for editable installs), then you need to make sure thatninjais installed too.ninjais available onPyPIbut can also be installed at the system level, which is the recommended way to develop withmeson-python.As far as I can tell,
scikit-build-coremakes no attempt at setting upCMakefor you.astrofrog commentedon Feb 20, 2025
Ok so the main impact here as you said before too is that for editable installs we can no longer use build isolation. I think it might be an acceptable cost (at least from my perspective). What happens in practice if someone tries to do
pip install -e .. Will it give an error? Will it be clear what the developer has to do?neutrinoceros commentedon Feb 20, 2025
let me answer these with sample outputs I get from
gpgi(the package I just migrated to meson-python this week)yes
maybe not so much. This is the kind of error you'd get if you don't have build time requirements installed already (truncated)
I note that the experience can be a little bit improved for developers using
uv pipinstead ofpip, provided the following settingshere's the full output one gets with this set up
rgommers commentedon Feb 20, 2025
It does, in the same way as
meson-pythonwill installninja,scikit-build-corewill installcmakefor you from PyPI if it isn't already installed system-wide. I thinkmeson-pythonstill has a significant advantage here, becauseninjais ~300 kb andmeson1 MB, whilecmakeis 30-50 MB so downloading it all the time is painful. But if you're on a fast connection, the experience frompip installis similar.eli-schwartz commentedon Feb 20, 2025
Don't forget that some pretty funny things can happen if you try building on a system without a cmake wheel available -- it's available for Linux i686/x86-64, ppc64le, s390x, and armv7l/aarch64 only, and not all of those either if for some reason you insist on building new software on old systems with old glibc.
The same thing is true for ninja of course, which both mesin and cmake need, although I suspect ninja is more commonly installed.
Either way: you'll end up attempting to build the affected package from source too, so it really is quite good to have it preinstalled.
...
@rgommers I wonder if it would be possible for meson-python to detect build isolation and either provide a helpful error message or at least degrade the editable loader to print a warning about C/C++ not being updated then continue instead of erroring out?
DougBurke commentedon Mar 1, 2025
So, I'm trying to see if I can get sherpa to build with meson-python rather than setuptools and it looks like I've got most major issues working in sherpa/sherpa#1948
One interesting issue was that pytest assert rewrites don't seem to fire when using editable builds, which breaks some tests (these are tests of our test code, so no functional change, but still annoying). I leave
in case it might be relevant here (and because until I found it I was just wondering if my understanding of computers was all wrong....).