본문 바로가기

내가 작업한 것들/소프트웨어

MacOS Apple Silicon 에서 universal binary 만들기

읽기에 앞서,

 이 방법은 X-Code IDE 를 쓰시는 분들을 위한 글이 아니라 iTerm2 또는 기본 Terminal app 과 X-Code command line tool 의 llvm-g++ 을 이용해서 빌드를 하는 환경을 기준으로 설명을 하는 글 입니다. 혹시라도 IDE 를 쓰시는 분들은 설정 관련으로 Apple 의 Universal binary 문서를 참조 해 보시기 바랍니다.

Apple universal binary

Mac OS universal binary logo

  Mac OS 는 Universal binary 라는 형태를 지원 하고 있습니다. 이 형태가 중요해 진 시점은 바로 Apple Silicon platform 이 상용화 되면서 M1 프로세서를 기반으로 한 Mac OS 11 big sur 가 대중앞에 나오기 시작하면서 부터 입니다. 정확히는 이 형태가 2005년 부터 Apple 에서 지원되는 고전적인 형태 이었으나, Apple 이 고 스티븐 잡스 시절 PowerPC 로 부터 Intel platform 으로 옮겨지면서 사실 큰 의미가 없어진 상태 였습니다. 아마 필요한 부분이 있었다면 32bit 와 64bit 를 동시에 지원하기 위한 universal 이 있었을지 모르겠지만, 실제 이런 형태가 배포되는 경우는 거의 없었던 듯 합니다.

 

 Mac OS 11 부터 중요해 진 점이 하나 있다면 바로 과도기에 멈춰 서 버린 Intel 로 부터 Apple 이 설계한 Silicon platform 인 M1 프로세서의 이동이 시작된 점이 아닐까 합니다. 그간 Intel 만능설에 물들어 있던 업체들이나 사용자들에게 있어서 ARM64(aarch64) 는 그저 휴대폰에나 들어가는 AP(Application Processor) 정도로 봐 왔을지 모르겠지만, 구조적으로 수많은 레지스터를 병렬로 처리하고 Big/Little 구조를 백분활용 할 수 있으며, 내부 BUS 를 직접적으로 제어할수 있는 등의 많은 장점을 가진 강력한 아키텍쳐가 임베디드나 휴대폰이 아닌 일반 랩탑/데스크탑 에 적용 될 경우 작은 배터리와 발열을 제한하며 동작해야 했던 환경과 달리 최고 성능을 끌어 낼 수 있는 환경에 기반을 할 수 있게 된다는 점은 큰 차이를 만들어 낼 미래가 아니었을까 했으며, 실제 M1 이 적용된 제품들을 사용자들이 써 보고 나서는 기존의 Intel 에서 느끼던 모든 경험이 M1 에 비할바가 못된다는 것을 알게 되었을 것 입니다.

 

 이런 상태에서 취미로 하나의 소스로 각 OS 를 모두 돌리는 것을 취미로 만드는 제 입장으로선 소시적 부터 돌려 쓰던 FLTK 는 물론 다른 여러 library 소스를 최신 OS 인 Big Sur 부터 지원되는 x86.64 (Intel) 과 ARM64 (M1) 을 동시에 지원하는 universal binary 를 만들 수 있도록 해야 했습니다.

 

핵심요소

 X-Code 는 여러 컴파일러 형태를 clang 으로 부터 llvm-gcc collection 까지 모두 지원을 해 주고 있습니다. 사실 llvm-gcc 를 호출해도 clang 이 호출되는 형태이긴 합니다만, 타 OS 와 크게 차이를 두고 싶지 않기 때문에 llvm-gcc 기준으로 설명을 합니다.

 먼저 이 universal binary 는 c 나 c++ 소스로 부터 만들어 질 때 부터 지정이 되어야 하며, 다음 한줄이 그 핵심 입니다.

-arch x86_64 -arch arm64

 이게 무슨 의미인가 하면, c/c++ 을 컴파일 해서 object 나 바로 실행 이미지를 만들때 컴파일러 옵션 지시자에 위 한줄이 포함 되어야 한다는 의미 입니다. 만약 Makefile 을 만든다면 다음처럼 기능을 추가 할 수 있습니다.

# Compiler configure.
GCC = llvm-gcc
GPP = llvm-g++
AR  = ar
LT  = libtool
RLB = ranlib
LIP = lipo

# Check architecture for x86.64 or arm64.
ARCH = $(shell uname -m)

 ARCH 는 실제 현재 시스템의 shell 에서 uname -m 을 통해 얻어온 정보를 가지게 되는데, M1 에 올라가 있는 Mac OS 는 여기서 arm64 라는 문자열을 가지게 됩니다. 그래서 다음과 같이 Makefile 내에서 조건문을 통해 architecture 지시를 할 수 있게 됩니다.

CPUARCHOPT =

# architecture flag setting.
ifeq ($(ARCH),arm64)
CPUARCHOPT += -arch x86_64 -arch arm64
endif

이제 CPUARCHOPT 에 arm64 일 경우 universal binary 를 만드는 옵션이 들어 가게 됩니다. 그리고 이걸로 만약 test.cpp 를 컴파일 하게 된다면 ?

bin/test: src/test.cpp
	@$(CPP) $(CFLAGS) $(CPUARCHOPT) $(LFLAGS) -o $@

이런식으로 지정이 될 수 있게 됩니다. 이렇게 만들어진 파일을 file 로 확인 해 보면 다음처럼 나오게 됩니다.

>  file bin/test
bin/test: Mach-O universal binary with 2 architectures: [x86_64:Mach-O 64-bit executable x86_64] [arm64]
bin/test (for architecture x86_64):	Mach-O 64-bit executable x86_64
bin/test (for architecure arm64):	Mach-O 64-bit executable arm64

 필요에 따라 더 많은 architecture 를 추가할 수도 있을지 모르겠지만, Big Sur 상에서 X-Code 상에서 지원된는 target 은 위 두가지 뿐 입니다.

 

Static library 를 만들때 주의점

 이번에 fl_imgtk 라이브러를 만들면서 겪은 문제인데, 기본적으로 obj 를 각자 만든 다음 이걸  ar 로 하나로 묶는 방법을 사용해 왔습니다만, 단순이 이렇게 해선는 Link 단계에서 다중 architecture 를 인지하지 못해서 컴파일이 중지 되는 경우를 겪게 되었습니다. 그래서 기존엔 ar -q 옵션으로 여러개의 o 를 하나의 a 로 만들었다면, 이를 다음과 같이  ranlib 을 하나 더 써서 static library 를 참조해서 빌드하는 gcc 에서 이를 제대로 인지할 수 있도록 정리를 해 줘야 합니다.

GCC = llvm-g++
AR = ar
RANLIB = ranlib
TARGET = lib/test.a

${OBJS}: ${SRC}/%.cpp
	@$(GCC) -c $< $(CFLAGS) $(CPUARCHOPT) -o $@

${TARGET): ${OBJS}
	@$(AR) -cr $@ $^
	@$(RANLIB) -q $@

 위와 같이 ar -cr 로 만들어진 o 파일들을 static library 인 lib/test.a 파일로 만든 다음 ranlib 으로 정리를 해 줘야 합니다. 만약 shared library 를 만든다고 하면 ar 과 ranlib 대신 $(GCC) -shared $^ $(CPUARCHOPT) $(LFLAGS) -o $@ 로 만들어 주기만 해도 됩니다. 물론 MacOS 에서는 so 대신 dylib 확장자를 써 주는 것이 좋습니다.

 

마치며

 이 방법 외에도 lipo 나 libtool 등을 이용해서 이미지를 생성 하는 방법들이 있으나, 개인적으로 고전적인 Makefile 로 차례로 빌드하고 합치는 방법을 즐기는 정도로만 설명을 하였습니다만, 이해하기 전 까진 꽤 어려운 작업일 수 있었을지 모르나, 실제 방법을 알고 나면 어려울것도 없도록 만들어 놓은 것이 Apple 측의 설계 입니다. 이렇게 만들어진 첫번째 공개 프로그램은 Make Image O' HDR version 0.2.24.81 이며 다음 링크에서 최신 이미지를 받으실 수 있습니다.

위 Link 에서 Mojave 이상의 OS 에서 MIOHDR_MacOS_universal_app_v0.2.24.81.zip 파일을 받아서 앱을 설치 후 실행 하면 문제없이 최신 버젼의 Make Image O' HDR 이 구동 되는 것을 확인 할 수 있습니다. 특히 이 버젼은 기존에 버그를 수정하고 있지 못하던  log mapping 알고리즘 색상 깨지는 문제와, 병렬 처리 성능을 더욱 더 향상 시킨 성능으로 Windows 보다 빠르고, Intel 보다 M1 이 월등히 빠른 처리 성능을 가지고 있음을 경험 하실 수 있습니다.