Universal build for Mac
Here are simple steps you can follow to build a universal
command-line program in the Terminal, outside Xcode. We'll use
make
, clang
to compile and link source
code, and lipo
to merge programs compiled separately for
x86 and arm64 (M1 to M4) into a single file.
We assume that you have installed Xcode, available for free on the Apple Store, and opened it at least once. The first time you launch it, you should be asked to install the development tools. We also assume that you know how to use the Terminal and have a basic knowledge of the basic Unix development tools such as gcc and make.
Compiling a universal program is made in three steps:
- Compile for Intel with the C compiler
clang
using an explicit x86_64 target architecture - Compile for Apple Silicon with
clang
using an explicit arm64 target architecture - Merge with
lipo
You don't need to specify the native target architecture of the Mac where you compile your program, but there is no harm in doing so.
In the simplest case with a single source file,
program.c
could be compiled as follows:
clang -o program.x64 -target x86_64-apple-macos13.0 program.c clang -o program.a64 -target arm64-apple-macos13.0 program.c lipo -create -output program program.x64 program.a64
For larger programs, we propose the following generic makefile, with rules to compile separately source code to each architecture: universal.mk
CC = clang CXX = clang++ CFLAGS_X64 = -target x86_64-apple-macos13.0 CFLAGS_A64 = -target arm64-apple-macos13.0 $(TARGET): $(TARGET).universal mv $< $@ $(TARGET).x64: $(SOURCE:.c=.x64.o) $(TARGET).a64: $(SOURCE:.c=.a64.o) %.universal: %.x64 %.a64 lipo -create -output $@ $^ %.x64: $(CC) $(CFLAGS_X64) -o $@ $^ %.a64: $(CC) $(CFLAGS_A64) -o $@ $^ %.x64.o: %.c $(CC) $(CFLAGS) $(CFLAGS_X64) -c -o $@ $< %.a64.o: %.c $(CC) $(CFLAGS) $(CFLAGS_A64) -c -o $@ $< %.x64.o: %.cpp $(CXX) $(CXXFLAGS) $(CFLAGS_X64) -c -o $@ $< %.a64.o: %.cpp $(CXX) $(CXXFLAGS) $(CFLAGS_A64) -c -o $@ $< .PHONY: clean clean: rm -f *.x64.o *.a64.o *.x64 *.a64
To use it with your files file1.c
,
file2.c
, etc., write your own makefile and include it:
SOURCE = file1.c file2.c file3.c TARGET = program # define vpath, VPATH, CFLAGS etc. as usual .PHONY: main main: $(TARGET) include universal.mk
Typing make
will:
- compile each source file .c to .x64.o and .a64.o object files
- link them separately to .x64 and .a64 programs
- merge them to a universal program.