mesonbuild / meson-python

Meson PEP 517 Python build backend
https://mesonbuild.com/meson-python/
MIT License
118 stars 59 forks source link

macOS: fix detection of nativearch for PowerPC #553

Closed barracuda156 closed 2 months ago

barracuda156 commented 6 months ago

(If this can be done in a simpler way, please advise me. Python is not my sphere of expertise.)

Problem

platform.mac_ver() on PowerPC is not giving a value which the code expects (this is in Rosetta):

macmini:macports-ports svacchanda$ /opt/local/bin/python3.11 -c 'import platform; print(platform.mac_ver())'
('10.6.8', ('', '', ''), 'PowerPC')

This makes the build system believe it is cross-compiling, and if the cross-file is not provided (and in Numpy it is not, for example), disaster ensues: https://trac.macports.org/ticket/68908

What is giving the correct values is sysconfig.get_platform() used in the same file just above:

macmini:macports-ports svacchanda$ /opt/local/bin/python3.11 -c 'import sysconfig; print(sysconfig.get_platform())'
macosx-10.6-ppc

It is defined for PowerPC here: https://github.com/python/cpython/blob/48c907a15ceae7202fcfeb435943addff896c42c/Lib/_osx_support.py#L566-L572

dnicolodi commented 6 months ago

I don't understand which problem you are trying to solve. The code you modify is executed only if the $ARCHFLAGS environment variable is set. This environment variable is used for cross compilation. Why are you setting the $ARCHFLAGS variable when apparently you are compiling on on PPC for PPC? What does Rosetta have to do with this? Do you mean that you are running a Python compiled for x86_64 on PPC via Rosetta?

barracuda156 commented 6 months ago

The code you modify is executed only if the $ARCHFLAGS environment variable is set.

For every architecture, AFAIU, nativearch value from platform.mac_ver() will be identical to build arch value (when they are actually the same), but not for PowerPC (where the arch value is ppc/ppc64, but platform.mac_ver() gives out PowerPC or Power Macintosh). ARCHFLAGS may be there in environment for different reasons, not necessarily cross-compilation. Why should they break something?

What does Rosetta have to do with this

This was primarily a clarification (for those who may wonder how come I have ppc on 10.6.8). Also the value for machine on native PowerPC is usually Power Macintosh (but cython accounts for that).

dnicolodi commented 6 months ago

ARCHFLAGS may be there in environment for different reasons, not necessarily cross-compilation. Why should they break something?

$ARCHFALGS is supported in meson-python only for the purpose of cross compilation. What else do you want to do with it? To which value do you set this environment variable?

barracuda156 commented 6 months ago

ARCHFALGS is supported in meson-python only for the purpose of cross compilation

Not an exhaustive list, but:

  1. Since it is an enviroment variable, it may be set without a user doing anything pro-actively during the installation.
  2. Rosetta / Rosetta2 will need that (and yet it is not cross-compilation in a sense the term is used).
  3. Macports sets it for linking to work correctly, from what it looks.

To which value do you set this environment variable?

In my case it will be -arch ppc (or -arch ppc64 if built for ppc64).

dnicolodi commented 6 months ago

Which tools do look at the $ARCHFLAGS environment variable? AFAIK it is a setuptools invention. Therefore, if you are not building with setuptools, there is no reason to ever set it. Your patch makes $ARCHFLAGS have no effect at all, thus I don't understand why you say that it is required to set it.

barracuda156 commented 6 months ago

I readily admit that my proposed fix may not be optimal, so if there is a better way to do this, let’s change it.

My concern is the following:

  1. The existing code conditions on something which evaluates as false in 100% of cases (for PowerPC) (I meant those values are never equal; condition is ≠, so true in 100% of cases, but anyway). If it has been already established that we are cross-compiling, then at best this condition is redundant. If it is not redundant, then it does not work correctly for a case of macOS PowerPC.

  2. While admittedly this problem may ever affect very few people, it breaks the build in a rather non-obvious way, with wrong compiler being picked, in violation of environment settings, and nothing in the error output says what to do. Yes, we could fix it locally in Macports, but then someone else eventually wastes a day trying to figure out what goes wrong.

barracuda156 commented 6 months ago

@dnicolodi Provided that condition is not redundant (which, I assume, is the case), what we want – hopefully – is for it to actually work, for every arch. For PowerPC there are two archs (ppc and ppc64), and compilers on Darwin use respective flags: -arch ppc and -arch ppc64. AFAIU, the code strips the flags to convert into build arch values (which is correct). What is not correct is to use machine to infer the arch: – it never differentiates between the two (there is no possible way for it to guess which arch one wants, the return value is always Power Macintosh (or PowerPC); – return value from machine will never be equal to build arch: they are different strings.

Due to the former issue it will not work to mechanically define something like PowerPC == ppc. Instead, maybe a code from cython can be borrowed, if relying on a ready-made function does not work for needed purposes.

(Perhaps I should have explained a bit in detail from the beginning, sorry.)

dnicolodi commented 6 months ago

I don't understand what you are trying to do, what the behavior you observe is, and what the behavior you want should be. Piecing together the information you provided, I understand that you compile with $ARCHFLAGS set to -arch ppc or -arch ppc64 and you expect this to be reflected in the flags passed to the compiler during package build. If this is the case, the code is working just fine as it is: it detects that the value for the -arch options passed in $ARCHFLAGS is not the system architecture, and it synthesizes a cross file to pass to meson specifying the desired -arch flag. If you don't want the -arch flags passed to the compiler, just don't set the $ARCHFLAGS environment variable.

In particular, this statement does not reflect how the code you are modifying behaves:

This makes the build system believe it is cross-compiling, and if the cross-file is not provided (and in Numpy it is not, for example), disaster ensues:

If meson-python detects that $ARCHFLAGS is used to specify an architecture that is not the system one, it synthesizes a cross file and passes it to Meson. Therefore the user is not expected to provide one. I don't see what this has to do with the linked bug report where the build fails because the cython compiler is not found.

barracuda156 commented 6 months ago

it detects that the value for the -arch options passed in $ARCHFLAGS is not the system architecture

But it is. I do not know what to add to already explained above. There is no archflag “PowerPC”. There is no machine value ppc or ppc64. Comparing these two is a meaningless endeavor: the condition of non-equality is always true, since these values are never identical. Is it supposed to be always true? Then it can be just removed with no consequences.

That is, logically it is just wrong. In practice, independently of logic, it breaks the build.

it synthesizes a cross file and passes it to Meson. Therefore the user is not expected to provide one.

Well, it does not work this way. To begin with, it misdetects that the arch is different from a native one, as I tried to explain above. Secondly, it fails to generate correct cross-file, and the build fails.

P. S. How would one specify an architecture to build for on a PowerPC machine with G5 cpu and macOS 10.5.x without an archflag (or other way to set the arch, like a triple)? Both ppc and ppc64 are native, there is no cross-compilation whatsoever happening. Both OS and hardware support building and running ppc and ppc64 binaries natively. I am not an expert with Intel, but I think it is a similar case with i386 and x86_64 binaries on x86_64 hardware with macOS 10.6+.

dnicolodi commented 6 months ago

There is no archflag “PowerPC”. There is no machine value ppc or ppc64. Comparing these two is a meaningless endeavor: the condition of non-equality is always true, since these values are never identical. Is it supposed to be always true? Then it can be just removed with no consequences.

The check is there for a reason, for other architectures. The only consequence of it being true is that meson-python synthesizes a cross file with the -arch ppc or -arch ppc64 if that is what $ARCHFLAGS contains. Again, if you don't wish to have this flags passed to the compiler, don't specify them in $ARCHFLAGS.

That is, logically it is just wrong. In practice, independently of logic, it breaks the build.

Can you please provide a build log demonstrating the failure? The one in the bug report you link reports that cython or cython3 are not found, which has nothing to do with compilation flags.

How would one specify an architecture to build for on a PowerPC machine with G5 cpu and macOS 10.5.x without an archflag

The G5 is a ppc64, thus I'm almost sure that the compiler emits ppc64 code if you don't add any compiler flag.

barracuda156 commented 6 months ago

The check is there for a reason, for other architectures.

So why should it be left broken for PowerPC then? It does not work now simply because nobody was aware of this and no one tested or paid attention to a failure. It is totally fixable (whether via my fix or elsewise). We just need to make the build system aware of correct definitions.

The only consequence of it being true is that meson-python synthesizes a cross file with the -arch ppc or -arch ppc64 if that is what $ARCHFLAGS contains. Again, if you don't wish to have this flags passed to the compiler, don't specify them in $ARCHFLAGS.

At least in three cases archflags are needed: building for ppc64, building FAT and building on Rosetta.

Can you please provide a build log demonstrating the failure? The one in the bug report you link reports that cython or cython3 are not found, which has nothing to do with compilation flags.

Yes, I will once on the machine. However the issue, I think, is the following:

  1. Meson misdetects cross-build.
  2. That triggers generating cross-file and redefining setting accordingly, which discards path to Cython, which Macports passes under assumption of a native build.
  3. Since meson generates wrong settings, build will break even if Cython is found, since wrong compiler and linker are picked, which do not support needed standard. So Cython issue is incidental.

Why it did not fail before was that pep517 was not used, and it looks like Macports used a wrapper to fix Meson behavior with cross-file. (I need to verify how exactly it was fixed, but as a matter of fact pep517 is broken, including on x86_64 up to 10.7, while non-pep517 build works fine.)

The G5 is a ppc64, thus I'm almost sure that the compiler emits ppc64 code if you don't add any compiler flag.

AFAIK otherwise, I will check on 10.5, but the point is that archflags or target is needed there, since two archs are native, and machine value makes no difference between them.

barracuda156 commented 6 months ago

The G5 is a ppc64

G5, while being a 64-bit cpu, supports both ppc and ppc64 natively. OS running on it may or may not. image

dnicolodi commented 6 months ago

I will explain it once more, maybe I was not sufficiently clear before. There are two possibilities: you need to pass -arch ppc or -arch ppc64 to the compiler, or you do not.

If you do, you can set the $ARCHFLAGS environment variable to -arch ppc or -arch ppc64. When you do, meson-python synthesizes a cross compilation environment definition and passes it to meson via the --cross-file option. Because people likes to do stupid things, meson-python checks whether the architecture passed to the -arch option in $ARCHFLAGS is different from the system native architecture. If it is the same, it skips the generation of the cross compilation environment definition and does not pass the --cross-file option to meson. Therefore, as not other piece of software looks at the $ARCHFLAGS environment variable, the case in which the value passed to -arch in $ARCHFLAGS and the system native architecture are the same, has no effect on the compilation.

What your patch does, is to make meson-python detect that the native architecture for OSX on PowerPC is ppc or ppc64. This in turn makes meson-python skip the generation of the cross compilation environment definition and passing of the --cross-file option to meson when you set $ARCHFLAGS to -arch ppc64 on your G5. This is absolutely identical to do not set $ARCHFLAGS.

However, you insist that you need the proposed patch and you need to pass -arch ppc64 to the compiler. This cannot be correct, as the effect of your patch is to do not pass the flag to the compiler. If you don't need the flag, don't set $ARCHFLAGS. If you do need it, your patch is wrong.

barracuda156 commented 6 months ago

A quick update from powerpc machine and numpy 1.26.2:

  1. Removing ARCHFLAGS from the portfile makes the build recognized correctly as native. So this does work in a trivial case. (However it will not work for ppc64 and Rosetta, likely for universal too.)
  2. If ARCHFLAGS are passed in environment, build is misdetected as cross, and breaks, since meson picks the wrong compiler and linker. So cross-build without user-provided cross-file is broken on macOS PowerPC.

The log is for the second case: main.log

barracuda156 commented 6 months ago

However, you insist that you need the proposed patch and you need to pass -arch ppc64 to the compiler. This cannot be correct, as the effect of your patch is to do not pass the flag to the compiler. If you don't need the flag, don't set $ARCHFLAGS. If you do need it, your patch is wrong.

As per your comment above re return values of sysconfig.get_platform(), I agree, my patch will not work in a general case and should not be merged in this form.

Then, the question can be put another way: can we fix cross-file generation, so that compiler and linker are picked from environment and not randomly from system prefix? If it is not possible, well, I guess we will need to add our cross-file and patch meson.build to use specifically that one. (It is not essential whether a native ppc/ppc64 build is misdetected as cross, after all, if in result the build gonna work nevertheless.)

dnicolodi commented 6 months ago

I am tired of repeating myself. You seem to attribute some magic property to the $ARCHFLAGS environment variable, which are obviously not there. $ARCHFLAGS is the wrong solution for your problems. Patching meson-python is the wrong solution for your problems. You don't need to patch anything to pass a cross compilation environment definition describing your setup to meson through meson-python.

barracuda156 commented 6 months ago

@dnicolodi I agree with you that setting ARCHFLAGS is not needed in a trivial case. I have verified that works, at least on ppc, as I mention above. There is nothing to fix for this case.

What I do not get is how do you suggest to handle a case when there are more than 1 native archs supported by the OS. Maybe I am phrasing it in a confusing way, I apologize if so.

Suppose a version of gcc supporting 2011 standard is built as universal (ppc+ppc64) on 10.5.8 on a G5. That is, hardware, OS and gcc support two archs natively. If I do not pass archflags (or use another way to set the needed target), presumably some one arch will be built with no ARCHFLAGS being set (I think it will be ppc, but in any case it can be only one choice of the three, whether deterministic, hopefully, or random). I may need to build for a different arch, or build universal (which requires to build each of the archs separately and then use lipo). How do I do that? If I set ARCHFLAGS, the build will be detected as cross, and we have already seen that it does not work, because meson picks a wrong compiler when generating a cross-file (the build is started with gcc13, cross-file randomly picks gcc-4.2 from system prefix, which will not build numpy).

If you say that I will need a custom cross-file in any such a case, okay, I can accept that; otherwise I do not see how it is supposed to work.

Because people likes to do stupid things, meson-python checks whether the architecture passed to the -arch option in $ARCHFLAGS is different from the system native architecture. If it is the same, it skips the generation of the cross compilation environment definition and does not pass the --cross-file option to meson.

Just to reiterate, this does not work in a described way on PowerPC: meson fails to realize it is a native build when ARCHFLAGS is passed. Fair enough, you can say it is a fault of a user to pass ARCHFLAGS, but nevertheless the code does not work as it is supposed to work. Whether it should be fixed or not, is up to you and other meson developers to decide, of course.

erikbs commented 5 months ago

Would it be possible to use -m32 and -m64 instead of specifying -arch ppc and -arch ppc64?

barracuda156 commented 5 months ago

@erikbs If you mean whether than gonna work for macOS with a native build, then yes, AFAIK is does.

It will not work for FAT builds if one arch is of a different family (i.e. something like i386 + ppc).

erikbs commented 5 months ago

Ah, I think I understand.

So just like system libraries are fat binaries containing both x86_64 and aarch64 code on recent macOS versions, older Mac OS X versions had fat system libraries for ppc, i386 and x86_64. On an older system I can pass -arch i386 to compile my program for Intel and -arch ppc to compile for PowerPC, and both will be linked to /usr/lib/libSystem.B.dylib etc. There is no cross-compilation and no need to set -isysroot etc. (as when cross-compiling for iOS). I can use lipo to create a fat binary that can be transferred between Intel and PowerPC Macs and run on both.

If I understand correctly, meson-python will not cross-compile if passing -arch i386 on an Intel Mac (with a 32-bit OS), but it will initiate cross-compilation if passing -arch ppc, then fail because it tries to cross-compile and fails to find the libraries, compiler etc.? If passing -arch i386 on x86_64, meson-python will cross-compile, but if passing -m32 it will just build in 32-bit mode without cross-compiling, whilst Clang treats these options as exactly the same?

I agree that when Apple’s developer tools allow (require?) you to do this without cross-compilation, it is strange that meson-python instead enforces cross-compilation for one of the architectures. Does it even make sense to let architecture alone determine cross-compilation on Mac? After all, compiling an aarch64 iOS app on aarch64 macOS requires cross-compilation, even though the architecture is the same.

I also wonder what happens if you pass multiple -arch flags to meson-python. If I call clang -arch x86_64 -arch aarch64 … on my macOS 11 system, a fat binary (x86_64+aarch64) is produced. What will meson-python do? Will the order matter?

Anyway, given that the Mac operating system itself is (or at least was) identical regardless of architecture and that the hardware will/would simply boot the code matching its architecture, could one argue that the OS has more than one “native” architecture? For clarity we could of course call them “supported architectures” or something instead.

Could it then be an option to let platform.mac_ver() also return a list of supported architectures (would be ppc+ppc64+i386 on 10.5, i386+x86_64 on 10.9, x86_64+aarch64 on 11 etc.) and replace the cross-compilation condition if arch != nativearch with if not arch in supportedarchs?

It is a more general solution that does not check explicitly for ppc/ppc64. The list of supported architectures per Mac OS version could be hardcoded or determined from e.g. /usr/lib/libSystem.B.dylib.

dnicolodi commented 5 months ago

It will not work for FAT builds if one arch is of a different family

meson-python does not support FAT or unified builds.

barracuda156 commented 5 months ago

@dnicolodi @erikbs Perhaps my last comment has brought about a bit of a confusion.

There is no expectation that meson-python is, will or should support FAT builds. It is also unnecessary for our purposes (because Macports can handle separate builds for every arch and then lipo those – ports do not have to have any kind of support for FAT builds from their side) and largely useless to begin with, since modern gcc does not support FAT builds as well.

The only issue we have, IMO, is that meson-python behaves differently when building on a PowerPC in described scenarios, since it detects the platform to be Power Macintosh, but build arch to be ppc, which leads to an incorrect assumption of this being a cross-build. Assumption of cross-build combined with a lack of needed cross-file leads to a wrong compiler and linker being picked, which in turn breaks the build, when specific compiler is required (typical case is a need for C++11 or higher), different from a system one (gcc-4.2).

I do believe this can and should be fixed on meson-python side, but obviously I am not making decisions here, it is up to the upstream.

It is still possible to hack around this bug and make it work correctly from Macports side.

dnicolodi commented 5 months ago

@erikbs cross compilation via the $ARCHFLAGS environment variable on macOS works just fine with meson-python for compiling aarch64 binaries on x86_64 systems and the other way around. Universal binaries in wheels (wheels build for multiple architectures) are a bad idea for a number of reasons and are not supported. meson-python raises an error if multiple -arch options are specified in $ARCHFLAGS.

I have yet to understood what exactly @barracuda156 is trying to do, despite multiple requests to him to clarify what his goals are. What we have so far are just statements saying that setting $ARCHFLAGS does not work for him. The only thing that could be actionable is the observation that settign $ARCHFLAGS="-arch ppc64" on a system where ppc64 is the native architecture results in meson-python generatign a cross file for meson specifying this compiler option, despite it not being strictly necessary. However, this should not have any adverse effect on the build. This nuisance is also not particularly difficult to fix. However, the meson-python developers have no access to a system where any patch fixing the problem could be tested: PowerPC is not an architecture supported by any current macOS release. Running such a system connected to a network is an inherent security risk, thus having access to one is extremely difficult. Although, if someone wants to send some PowerPC hardware my way, I can provide a delivery address. On the other hand, this nuisance can be completely avoided not setting the $ARCHFLAGS environment variable to bogus values.

Reading between the lines, it seems that what @barracuda156 is trying to do, is to cross compile a ppc binary on a ppc64 build host with a compiler that is not the one found as the cc binary in $PATH. The way to do that is to craft a cross, something like:

[binaries]
c = ['path/to/gcc', '-arch', 'ppc']
cpp = ['path/to/g++', '-arch', 'ppc']
[host_machine]
system = 'darwin'
cpu = 'ppc'
cpu_family = 'ppc'
endian = 'big'

and pass it to Meson using meson-python command line build settings:

python -m build -Csetup-args=--cross-file=cross.txt
dnicolodi commented 5 months ago

meson-python supports $ARCHFLAGS for compatibility with setuptools. It can work only for the very simple case where the only thing that is needed is to pass the -arch option to the default compiler. For anything more, Meson native files or cross files definitions must be used.

dnicolodi commented 5 months ago

I do believe this can and should be fixed on meson-python side, but obviously I am not making decisions here, it is up to the upstream.

We don't have any way to test a fix for this. The fix you proposed does not work and you didn't come up with an alternative. If you produce something that has at least a chance of being correct we may consider merging it.

dnicolodi commented 5 months ago

It is still possible to hack around this bug and make it work correctly from Macports side.

There is no bug to work around. You are using meson-python wrong. I'm kind of tired to repeat that if setting $ARCHFLAGS does not work for you, you should simply not do it, but you insist that you must set it, without providing any explanation for why you think this is the case.

barracuda156 commented 5 months ago

I do believe this can and should be fixed on meson-python side, but obviously I am not making decisions here, it is up to the upstream.

We don't have any way to test a fix for this. The fix you proposed does not work and you didn't come up with an alternative. If you produce something that has at least a chance of being correct we may consider merging it.

@dnicolodi Thank you. Let me see if it can be addressed in a correct way. I agree with you that proposed fix is not correct in a general case and should not be merged.

dnicolodi commented 2 months ago

This hasn't seen any activity since January, I don't know how the problem could be solved, and it is not really possible to get access to a system where this cab be tested as any macOS version that supports ppc is long out of support. I'm closing this. If someone is annoyed enough about this and has a reliable way to detect the macOS native architecture on ppc they are welcome to open a new PR.