Anton Serov
Anton Serov

Reputation: 302

macOS arm64 cannot find x86_64 versions of applications

My macOS 14.2.1 (Darwin 23.2.0) is arm64 by default. I set up a bunch of x86_64 versions for different apps (have Rosetta 2 set up as well). For example, cmake and Homebrew. But when I call them with arch -x86_64 prefix, the OS cannot find them:

arch -x86_64 cmake
arch: posix_spawnp: cmake: Bad CPU type in executable

The following thing works though:

arch -x86_64 /usr/local/Homebrew/Cellar/cmake/3.31.4/bin/cmake

The same is with the other apps installed for x86_64. Even when I call arch -x86_64 zsh and switch my bash to the x86_64 mode, it can't find the paths. When I call

which cmake

It always shows only one path (arm64), either when I'm on arm64 or x86_64 version of zsh:

/opt/homebrew/bin/cmake

Should I manually add all paths to the x86_64 applications to $PATH or is there a better way to tell the OS to do this automatically?

Upvotes: -1

Views: 68

Answers (1)

Siguza
Siguza

Reputation: 23820

This is not how slice selection works (which is what the arch binary and the underlying posix_spawnp option do). Slice selection works when you have a fat Mach-O with multiple architectures in the same file.

What you have is just different binaries with the same basename in different paths. When you run arch -x86_64 cmake and /opt/homebrew/bin/cmake is the first cmake in your path, then that is equivalent to invoking arch -x86_64 /opt/homebrew/bin/cmake, but /opt/homebrew/bin/cmake only has an arm64 slice, so there is no x86_64 slice to select.

One option would be to manually go through all the files that you have compiled for multiple architectures and use lipo to merge them into fat binaries, then put those in some location in $PATH that takes precedence over Homebrew - but I imagine this isn't very maintainable.

Another option would be to manually iterate through all matching binaries in $PATH, check if they have the desired architecture, and then run them. With zsh, this could e.g. be done with whence -pa and lipo -archs:

arm64()
{
    bin="$1";
    shift;
    whence -pa "$bin" | while read -r p; do
        if lipo -archs "$p" 2>/dev/null | fgrep -q 'arm64'; then
            arch -arm64 "$p" "$@";
            break;
        fi;
    done;
}

x86_64()
{
    bin="$1";
    shift;
    whence -pa "$bin" | while read -r p; do
        if lipo -archs "$p" 2>/dev/null | fgrep -q 'x86_64'; then
            arch -x86_64 "$p" "$@";
            break;
        fi;
    done;
}

Note that this intentionally does not use the -w flag with (f)grep, so that arm64e and x86_64h also count as matches. I'm sure you could combine those two and engineer them into a wrapper around arch, but if you wanted to support the full set of i386/x86_64/x86_64h/arm64/arm64e and -32/-64 flags, then there are a whole bunch of edge cases and defaults and preference rules that I'm not keen to dig into right now.

Upvotes: 0

Related Questions