Cross-compiling from Mac to Mac?

What is required to build an app that would work on all variants of macOS (for Intel, M1 and M2 chips, for instance) ?

Alternatively, is it required to recompile the app written in Crystal for all those platforms (respectively on each one of those platforms) ?

We are currently building the compiler for macOS using omnibus, bundling arm64 and x86_64 binaries together in a universal build. You can find the details in the distribution-scripts repository, in particular the ./darwin and ./omnibus folders. Note: it’s not trivial. You can also take a look into the specific commit that brought the universal build.

1 Like

So, I suppose I cannot simply use a specific command line option to generate a universal build.

Not at the moment. Note that you need the dependencies to be multi-platform too.


Not sure if your insinuating that, but I don’t think omnibus plays any particular role for this.

I’m not an expert on macOS but as far as I understand it, the process is technically relatively straight forward: You build two exectuables, one for x86_64 and one for aarch64, and then merge them together with lipo. Cross-compiling should be possible from either architecture to the other, so you can do all on a single machine. The tricky part is that you also need two versions of all the libraries.

1 Like

Yes, I was just pointing out that the scripts for omnibus contained the details.

1 Like

@straight-shoota is 100% correct. lipo will create a universal/fat binary for multiple architectures. I’ve got an old script on my machine which I used to use to merge PPC and Intel binaries about 15 years ago:


path=`pathname $1`
base=`basename $path`

if [ -e $path ]; then
	echo lipo -arch ppc /P$path -arch i386 $path -create -output combo/$base
	     lipo -arch ppc /P$path -arch i386 $path -create -output combo/$base
	echo cannot find $1

You can see that crystal is distributed as a universal

616 rmills@rmillsm1:/usr $ crystal -v
Crystal 1.7.2 (2023-01-23)
LLVM: 14.0.6
Default target: aarch64-apple-darwin22.3.0
617 rmills@rmillsm1:/usr $ lipo -info /opt/crystal/embedded/bin/crystal
Architectures in the fat file: /opt/crystal/embedded/bin/crystal are: x86_64 arm64
618 rmills@rmillsm1:/usr $

As mentioned, the challenge is to generate the binaries for the different architectures. I seem to recall (when I wrote, my PPC and Intel binaries were generated by platform buildservers (I had a PPC/Mac and Intel/Mac). I fetched the binaries with scp and creates the universal.

If you use this strategy, I think your binaries will work correctly as crystal compiled binaries depend on libraries in /usr/lib which are installed by Apple.

634 rmills@rmillsm1:~/gnu/crystal/hello/src $ cat
#!/usr/bin/env crun
# TODO: Write documentation for `Hello`
module Hello
VERSION = "0.1.0"

puts "Hello World!"

635 rmills@rmillsm1:~/gnu/crystal/hello/src $ /opt/crystal/bin/crystal build
636 rmills@rmillsm1:~/gnu/crystal/hello/src $ ./hello
Hello World!
637 rmills@rmillsm1:~/gnu/crystal/hello/src $ otool -L ./hello
/usr/lib/libiconv.2.dylib (compatibility version 7.0.0, current version 7.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 1319.0.0)
638 rmills@rmillsm1:~/gnu/crystal/hello/src $

By the way there’s Apple magic in macOS 11 and later (Big Sur, Monterey, Ventura) and files such as /usr/lib/libiconv.2.dylib are not on the File System. The dynamic loader dyld locates them in a secret and safe location (presumably for security reasons).

1 Like

I’ve thought a little more about this. In the good old days of PPC/Intel “fat” binaries, I could build PPC and Intel on the same machine. You could pass arguments such as -arch ppc to the compiler/linker (gcc in those days).

I believe LLVM will be similar.

So when crystal build calls LLVM, you should modify that code. You may have to invoke LLVM twice and use lipo to combine the results. It’s likely that LLVM (or Apple’s clang) can handle multiple architectures simultaneously.

Happy to get involved in working with you on this.