-
Notifications
You must be signed in to change notification settings - Fork 69
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 cross compiling #321
Comments
Thanks for identifying that issue @isuruf. It looks like we need to avoid this extension check, and also add a basic cross-compile CI job. I was just looking at that for SciPy. I'm not that familiar with |
I don't think calling this a bug is fair. Cross compilation is not supported by the Python packaging ecosystem. And apparently "crossenv monkey patches some things" to make it work, whatever this means, given that PEP 517 build are usually done out-of-process. The detection of the stable ABI is just one of the places where we introspect the running environment, and Meson does too. Python does not offer a supported way to obtain the information required to build an extension module for another interpreter, let alone for an interpreter running on another platform. If you need support for cross compilation, please bring the issue to the attention of the CPython developers. |
Fair enough, Python's support is very poor. But right now moving from We're not that far off, I'm just messing around with trying to get a Linux x86-64 to aarch64 cross compile to work for SciPy with:
I suspect it only requires a few tweaks. |
The first obvious issue I can think about is that we use |
The difference is that meson specifically executes an external python installation with a json dumper script, in order to scrape for information which meson knows it may need. It looks like meson-python checks for this information in-process instead, which means that it is bound to the same version of python that it is running inside. Cross compilation has two challenges:
|
Well, meson-python implements PEP 517, which does not have any provision for cross-compiling. It assumes that the Python interpreter used for the build, is the one you are building for. More generally, there is no way to generate wheel tags for an interpreter that you cannot execute. And if you can execute the Python you are building for, why not use it to run meson-python? I know all this is not ideal. But supporting these interfaces with these constraints is what meson-python is set up to do. If we want to build a tool for cross compiling Python wheels, it would have to look very different. |
Sure, I do understand and empathize with those issues. There is finally some interest in getting this to work once and for all, though, I think. :)
FWIW this is actually a complex topic. Particularly worthwhile to note:
But actually doing so is slow. So you actually don't want to do this, at least not more than you can get away with. So, there's a benefit to emulating just the json dumper and then running the rest of the build natively. |
I completely agree, but while it makes sense for Meson to work this way, I think it would be overkill for meson-python. But, because PEP 517, we don't even have to think about it: the interfaces we need to implement do not support this. |
There's several levels of making things work here:
(1) and (2) should be feasible in a shorter time frame. (3) is going to be a lot more painful. |
By necessity, conda-forge has built a lot of its packaging around cross-compilation (i.e. there aren't native osx-arm64 CI agents, so we need to cross-compile from osx-64). These builds might even flow back to the non-conda world occasionally. So it's a classic case of Hyrum's law, where a lot of things have grown up around an implicit interface (in this case the possibility to monkey-patch in cross-compilation), that we cannot switch the conda-forge builds for scipy to meson unless we solve the cross-compilation part somehow. I don't mind carrying patches or workarounds for a while (i.e. it doesn't need to have a sparkling UX), but it would be very beneficial to have it be possible at all. |
Cross compiling for an arm64 target on a x86 build machine on macOS is already supported with the same interface used by setuptools. For other user cases, I'm afraid that making the monkey-patches required to make cross compilation work with setuptools also work for meson-python is not possible, unless someone defines somewhere the interfaces that we can use and the ones we cannot. Even then, the thing would be extremely fragile without a clear definition of how the interface that we can use work when cross-compiling.
|
This is true, but the good thing is that the build envs are much better under control, and regressions are not as painful. We're looking to support distros here, not make |
Checking that the extensions modules filenames end with a suffix accepted by the current Python interpreter assumes that the wheel being assembled is for the same Python platform. This is not true when cross-compiling. The check protects against a very unlikely occurrence and makes life harder for tools that allow cross-compiling Python wheels. Remove it. See mesonbuild#321.
It saddens me a bit that people seem to think crossenv is the definition of how to cross compile. There are people who have been cross compiling python modules without knowing that crossenv exists (or I think in one case, being vehemently convinced that crossenv is horrible and the worst thing possible for the cross compilation community 🤷♂️). I think the reality is some combination of "a number of different groups have independently discovered some key aspects, and have different ideas how to do the rest".
Meson has a cross-compile interface. Meson defines how to cross compile a meson project. Frameworks used for cross compiling, including but not limited to crossenv, yocto, buildroot, voidlinux, etc, are responsible for interacting with the Meson cross-compile interface, and that is all. Meson, in turn, considers its cross compile interface to be "run a python interpreter and dump a specific list of values" -- this isn't well documented in the manual, but it does exist. (Those projects may have also homebrewed their own cross compile interface for setuptools, but that really doesn't matter for either meson-python or for meson. At the end of the day, the tricks they use are irrelevant to meson except for the sysconfig tweaking, and that's just parallel evolution, not something owned by a specific tool.) IMHO meson-python shouldn't fail to package a project that meson has successfully cross compiled, and for that reason I'm happy to see the check being removed. :) If you want to validate that the ext suffix matches the binary architecture, that seems like a job for auditwheel or something. If you need to generate a wheel tag, I actually feel a bit like that doesn't much matter. If you're building for local use you just immediately extract the results and discard the wheel tag in the process (and I assume conda has the same basic rationale to not-care) and again, I thought this was what auditwheel is for. If it can fix the manylinux version, I'd assume it can also fix a broken CPU architecture... can we just fall back on a nonsense wheel tag like "unknown"? Or lie and call it "none-any"? |
I don't think that.
I agree with most of what you wrote, but not this. It seems to me like |
@eli-schwartz I agree with you on all points, except one: the tools that takes a Meson build directory and packs it up in a wheel is not meson-python, but something else, that may or may not share some code and be implemented in the same project as the current meson-python. meson-python define itself has an implementation of PEP 517 https://peps.python.org/pep-0517/ In the interfaces defined in PEP 517 there is nothing that allows cross-compilation: it implicitly assumes that the wheels are being built for the Python interpreter running the build process. This is one of the reasons why solutions for cross compiling wheels have the taste of hacks: they need to work-around this interface limitation. AFAICT, auditwheel has the same core assumption, thus I don't think it can fix wheels built for an architecture that is not the one where it is being run. Building on @eli-schwartz consideration that the correct cross-compilation interface is the one provided by Meson, we need a tools that allows access to that interface (forgetting PEP 517). However, wrapping Meson in another tool dedicated to build Python wheels is not necessary, what the tool needs is just the Meson build directory. I'm thinking to something that could be run like this: meson setup $build_dir --cross-file $crossbuild_definition
meson compile -C $build_dir
meson-wheel-it-up $build_dir --tag $wheel_tag Where |
This would require determining which architecture we are building for from the compiler executable paths. I'm sure it can be done, but the user knows for which architecture they are building, they can just tell the wheel packaging tool about it. Also, it would require determining which flavor of python interpreter (cpython/pypy/pyston,... and the relative version) we are building for from the executable path. Also this seems an information that the user is in a better position to tell us. More problematic is the handling of build dependencies and (optional) build isolation implemented by PEP 517 frontends. You most likely do not want that for cross compiling. Build dependencies for cross compilation need to be correctly handled considering which dependencies are to be run on the host (cython, pythran) and which are libraries for the target (numpy, scipy, ...). PEP 517 frontends cannot do that, and they mostly get in the way. |
This is a little confusing to me. I'm not sure what you have in mind here exactly. Whatever other project it is, I think that is an implementation detail under meson-python. From my perspective only have two things: Meson as the build system, and What is and isn't in PEP 517 isn't that relevant, there's
It's not really an interface in the sense that
Not really - there's a section like this in the cross file: [host_machine]
system = 'windows'
cpu_family = 'x86_64'
cpu = 'x86_64'
endian = 'little'
That's a good point. I think the requirement here is "build interpreter kind == host interpreter kind" for now (in practice, the main demand is CPython). Possibly there's a need to add the host interpreter to the cross file - let's cross that bridge whne we get to it.
No one is going to use build isolation with cross compilation I think. It'll be something like:
There's nothing for us to do here to deal with build isolation AFAIK. |
The problem raised in this issue is about the host and the build interpreter being different (if they are the same, of course
I don't see what going through |
I meant "the same kind, so both CPython or both PyPy". That's a reasonable default, and I think conda-forge's cross compiling jobs for PyPy do that.
It's the only way to get the
They're already mandatory for many use cases. |
The PEP 517 backend is responsible for generating the meson-python/mesonpy/__init__.py Lines 556 to 568 in f9dc18f
|
@dnicolodi I'm not sure what you mean there.
It seems like you have a conceptual model here that I do not understand. If I understand you correctly, you have something other than Meson and meson-python in mind, but I don't know what that would be. |
My perspective is a bit different, but I think that's because I approach the whole issue from a different direction I view meson-python as two things:
And you mention "pip & co" but I think it's a bit simpler, and reduces down to "pip". Or maybe "PyPI". As I alluded to above, for local builds a wheel is a waste of time, and so are platform tags. What you actually want is a site-packages, and wheels are just a way for build backends to tell installers what files to install. System package managers like conda, dpkg, rpm, pacman, portage, etc. don't care about wheels, because they have better formats that provide crucial cross-domain dependency management among other things. They also, consequently, have better ways to define platform tags than, well, using anything as ill-defined and non-granular as platform tags. And what even looks at platform tags anyway? Just pip, basically... and, importantly, only in the context of downloading from PyPI. ... From the perspective of another package manager trying to repackage software produced by the pip package manager, wheels look like those makefiles where "make" creates a tar.gz file that you have to untar, and don't provide a "make install" rule.
But this does in fact tie into my belief that platform tags are also specifically to deal with distributing wheels on PyPI. The rest of the time it is a vestigial organ. While it can't hurt to get it correct where possible, this shouldn't come at the sacrifice of important functionality like generating a successful build+install. When in doubt, apply either a validating placeholder or a genetic tag that is technically correct but provides no information, like "linux" (is that a valid tag? Do you need the CPU architecture?) The result doesn't matter, if you're locally building the wheel, locally installing it, and locally creating a conda package out of it.
Because setuptools Because of the privileged position setuptools held in the ecosystem, this has become the new model for build backends, namely, that they shouldn't provide a command line. And ex post facto, this has been reinvented, rather than being due to peculiarities of egg-info, to instead be due to a host of imagined reasons for why command lines are bad, even as an alternative. ;) The result is that the advantage you get from going via Flit is fighting this trend, as It's a weird quirk but ultimately not a huge deal now that the core infrastructure has to some degree settled on flit. Note that any program which uses PEP 517 to generate a library API call to build a wheel, is a PEP 517 frontend. But not all PEP 517 frontends support build isolation, or default to it. For example, Gentoo Linux has written an internal frontend called gpep517, which is a "Gentoo pep517", that relies on the knowledge that Gentoo never ever wants build isolation. Yes, we're now seeing a proliferation of incompatible frontend command lines as a retaliatory response to the unification of backend APIs. And no, frontends aren't trivial to write either. |
Yes and no. All you do with the final zipfile is unpack it and throw it away, so from that perspective it doesn't do much. However, you do need the metadata installed. So a
Not as a distribution format, but they do in practice. These tools very often run
I completely agree that that's a mistake, and I appreciate Meson's nice CLI. But I think that's a mostly unrelated point here. If |
Right, like I said, meson-python does two things, and one of them is producing that metadata, and the other one is producing that wheel.
Right, this is very much a side topic in response to @dnicolodi's question about "I don't see what going through |
These files are not needed, Yocto creates its own native and cross files.
This chunk is indeed not needed, Yocto builds under Linux.
This chunk is needed. The file mismatch handling doesn't work particularly well because the native file is fed externally.
Already explained,
Yocto also passes the source and build directories, with the build directory being outside of the source.
purelib and platlib are not passed by Yocto and the build can fail because the native python's settings are not what the target build expects.
The build directory is created in advance and is different from what meson-python expects here.
I hope I explained everything. I can agree that none of this is needed for a straight build on the host which is also the target system, but Yocto's cross-compiler system needs them. |
|
Thanks, I will try these. Still, meson-python shouldn't set |
How does passing this arguments break cross compilation? They only tell Meson where to install some files. Then meson-python picks the files up from the specified location and packs them into a wheel. The content of the wheel does not depend in any way on these arguments. |
@zboszor You seem to be patching meson-python for the wrong reasons.
You can have as many native and cross files as you want. Meson merges them. You don't need to remove the arguments passed to Meson by meson-python to pass your own cross and native files.
If you leave the meson-python generated native file in place, you don't need to remove this code. However, this code will be gone in the next meson-python version.
This is an horrible idea, as @eli-schwartz explained. If you need to pass multiple arguments via python -m build -Csetup-args=-Cfoo=bar -Csetup-args=debug=true -Cbuilddir=/tmp/build3452
The argument to the
You can pass the build directory to use to meson-python, see the example above. The source directory is passed to meson-python by the Python build front-end (pypa/build or pip or whatever) there is no need to pass it separately.
As already explained, these settings should not have any effect on the content of the generated wheel.
I think that none of this is required, the patch is probably based on a misunderstanding of how meson-python works. |
Thank you for all the suggestions and constructive criticism. |
Great! It is nice to see that things works as designed also in an environment very different from the one where we usually test meson-python. Thank you for sticking with us and accepting the criticism! |
With the release of meson-python 0.13.0, our scipy builds in conda-forge now broke. They were previously relying on the apparently only accidentally-working circumstance that reconfiguration did not error, but in doing so, it allowed us to set up the compilation as needed, and not get tripped up by the Of course it would be good to avoid the reconfiguration (and we're trying to do that now in the linked PR), but AFAICT, that runs into the issue that meson-python unconditionally adds a
but will then nevertheless start compiling for x86 on aarch/ppc
It's completely possible that I misdiagnosed what's going on, but if I'm not too far off, it would be nice to have a way to switch off the automatic |
@h-vetinari I think you diagnosed it correctly. This seems easy to fix by specifying the desired (scipy-dev) $ cat native-file.txt
[binaries]
python = '/home/rgommers/mambaforge/envs/scipy-dev/bin/python'
(scipy-dev) $ cat cross-file.txt
[binaries]
python = '/home/rgommers/mambaforge/envs/cross-env/bin/python'
(scipy-dev) $ meson setup buildc --cross-file=cross-file.txt --native-file=native-file.txt
...
Program python found: YES (/home/rgommers/mambaforge/envs/cross-env/bin/python)
User defined options
Cross files : cross-file.txt
Native files: native-file.txt Note that cross files "layer", so specifying an extra cross file with only the python binary in it will not interfere with the other cross file that is obtained from the conda-forge infra. |
This is not what I think is happening. Most likely Meson is using the cross compilation toolchain to compile binaries for aarch64 but naming the Python extension modules for the Python interpreter that meson-python specified in the native file. PEP 517 and related PEP specify that the build needs to happen for the Python interpreter that is executing the Python build backend. meson-python uses the native-file for instructing Meson to compile for the interpreter that is executing meson-python. Meson uses introspects (executing some code with it) the interpreter and determines compilation flags and extension module filenames. meson-python similarly introspects the interpreter and determines the wheel tags. The wheel tags need to agree with the extension modules file names (and of course with the binary objects in these). Unfortunately, there is no specification for cross-compilation, other than running the build backend with the target Python interpreter (via emulation or something). Any other solution works against the specification and is destined to break from time to time as the build tools evolve. The solution proposed by @rgommers works (I think, I haven't tested it) for instructing Meson to build for the Python interpreter specified in the cross file. However, meson-python still introspects interpreter that is executing meson-python to generate the wheel tags, thus, if no other trick is applied (there are undocumented environment variables that affect what the introspection return just enough to trick meson-python to do what is expected in cross-compilation, at least on macOS and on Linux), the wheel tags will be off. If anyone figures out a reliable way to make all this work, I'll be very interested in adding the solution to the docs, and possibly in a test case on our CI infrastructure, so that we can at least be aware of things breaking. |
This might be a silly question (as I don't understand the whole design space here; if so, apologies), but since meson proper has cross-compilation figured out, why not piggyback on that in meson-python? As in: require that (when meson-python sees a cross-file) that it contain The absence of standards in this case might be a good thing for once, because that means you're not breaking anything or anyone by building on top of mesons cross-compilation interface. If a python standards ever appears, we could figure out how to transition to that, but that's rather pie in the sky right now, as opposed to the very real need to cross-compile python packages today. :) |
While cross compilation support is robust in general in Meson, unfortunately it isn't yet fully figured out for Python specifically. Before making more changes, I'd really like to see it finalized and documented in Meson itself: mesonbuild/meson#7049 (comment) |
In andyfaff/scipy#51 I'm trying to cross compile scipy with I can get scipy to complete the build step. I found I had to set When I install the wheel on I tried providing the
I know this cross-compilation process is still being worked on. If you have any tips for how I can proceed, or you would like to use that PR link to try and advance cross-compilation, I'm willing to work on it. EDIT: you can see a build log here, which also has a build artefact associated with it. |
Yes, this is the exact problem I was running into and reported on in detail in mesonbuild/meson#7049. This does work when using crossenv (as conda-forge does), but not without it. We're kind of stuck for the moment on the non- For more context, it is possible to get a non- On Void Linux, we set a lot of environment variables to tell the host Python to use the sysconfig data for the build arch and also add the build root to PYTHONPATH, allowing the host Python to find these modules and grabbing relevant information (field sizes, shlib suffixes, etc.) for the build arch rather than the host. |
... so, did anything change for this? |
Sorry not quite. There was an attempt to fix things in Meson (mesonbuild/meson#12190), which wasn't quite in the right direction. What we are really waiting for is for PEP 739 to be finished, that's basically the one thing we need (and I did review that and ensured it includes everything we need for Meson). We could actually implement support for the current, preliminary static file format in Meson and go with that. That's "just work", but it hasn't been done yet. I'll note that cross-compiling can be made to work, since Conda-forge and various Linux distros use it for packages like NumPy, SciPy and Matplotlib. It just doesn't have the kind of nice UX that we want for this issue and for official/documented support in meson-python. |
even I don't know what the use of meson-python is. cross-compiling using meson-python don't expect it to be easy |
Even though meson supports cross compiling, it seems meson-python does not.
meson-python/mesonpy/__init__.py
Lines 313 to 348 in 78861f5
We use crossenv to cross compile in conda-forge and crossenv monkey-patches some things but monkey-patching
importlib.machinery.EXTENSION_SUFFIXES
does not seem like a good idea.cc @h-vetinari, @rgommers, @eli-schwartz
The text was updated successfully, but these errors were encountered: