[Swift] Swift 빌드 과정

2024. 9. 8. 18:09·Swift

오늘은 Swift 빌드 과정에 대해 알아볼 생각이다. Xcode를 사용하면 빌드할 때 그냥 습관성으로 cmd + B 를 누르곤 했는데, 이 Build를 하면 어떤 과정으로 프로그램이 실행되는 지에 대한 내용이다.

Swift 빌드 과정을 알아보기 전 일단 전반적인(?) 프로그래밍 언어의 Build Process에 대해 알아보자. (내가 잘 모르니까 ~)

Build Process

Build Process가 뭘까?

우리가 코드를 작성하면 그 코드를 실행시킬 수 있는 실행파일이 필요하다. (그냥 바로 소스코드가 실행 ! 되는 것이 아니다..)

우리가 작성한 소스코드를 실행시킬 수 있는 파일로 만드는 과정을 Build라고 한다.

이 Build process는 아래 표와 같이 크게 3가지 타입으로 나눌 수 있다.

Type Process
Compile 소스코드 전체를 기계어로 번역
Interpreted 소스코드를 한 줄씩 번역하면서 실행
Hybrid 소스코드 전체를 중간(바이트 코드) 언어로 번역한 뒤 가상머신(VM)에서 한 줄씩 실행

우리가 아는 C, C++, Swift 등의 언어가 컴파일 언어이고, bash shell과 같은 터미널창에서 사용하는 shell script가 대표적인 인터프리터 언어라고 볼 수 있다.

뭔가 Python도 IDLE에서 사용했던 경험이 있어서.. 인터프리터 언어가 아닐까..? 했는데 찾아보니 어디서는 맞고 어디선 아니라고.. 흠.. 잘 모르겠다 ^_^

무튼 ! 컴파일 언어의 빌드 과정은 아래 사진과 같다. (난 Swift의 빌드 과정을 알아볼 예정이니 컴파일 빌드 과정만 간단하게 훑어보자는 마인드)

각 과정을 조금만 더 자세히 살펴보자

Preprocessing (전처리)

전처리 과정은 전처리기(preprocessor)에 의해 소스코드에 포함된 매크로나 지시자 같은 것을 포함시킨다.

즉, 소스코드의 main이 실행되기 전 사전준비를 하는 과정이라고 볼 수 있다.

Compilation (컴파일)

컴파일 과정은 우리의 소스코드가 저수준 언어(low-level Language)로 번역되는 과정이다. 보통은 어셈블리어(Assembly Language)로 번역된다.

이런 컴파일을 하는 프로그램을 컴파일러(compiler)라고 한다.

Assemble (어셈블)

어셈블 과정은 컴파일 과정을 통해 산출된 저수준 언어를 CPU가 읽을 수 있는 기계어로 번역해주는 과정이다.

이렇게 어셈블리어를 기계어로 번역해주는 프로그램을 어셈블러(Assembler)라고 한다. 그리고 이렇게 번역된 파일을 보통 Object File이라고 하는데, 번역하면 객체 파일이 아닌 목적 파일이라고 한다.

컴파일 과정을 좁은 의미로는 위에 나와있는 소스코드를 저수준 언어로 번역하는 과정이라는 의미로 사용할 수도 있지만, 넓은 의미로는 전처리 과정부터 어셈블 과정까지의 모든 과정을 통틀어 일컫기도 한다. Compile이라는 영단어의 뜻이 '번역'이기 때문에...

Linking (링크/링킹)

컴파일 과정을 통해 각 파일들이 기계어로 번역되었다면 이 번역된 각각의 파일들을 하나로 연결해주는 과정이 바로 링크이다.
링크를 통해 목적 파일(Object File)이 최종적으로 하나의 실행가능한 파일(executable file)이 된다.
(.exe라는 확장자 뜻이 실행 가능하단 뜻 ~)

그리고 컴파일 + 링크까지의 모든 과정을 통틀어 빌드(Build)라고 하는 것이다.

그럼 Build의 의미도 알았겠다 !! Swift의 Build Process를 알아보자 !!

Swift Build Process

위에서 알아본 일련의 빌드 과정에 필요한 프로그램들을 모아놓은 프로그램 모음을 Language processing system이라고 한다.
그리고 iOS나 macOS 개발에서 사용하는 Language processing system이 바로 Xcode Build System이다.

Xcode Build System을 포함한 대부분의 Language processing system은 아래 5가지 프로그램들로 이루어져있다.

  • Preprocessor
  • Compiler
  • Assembler
  • Linker
  • Loader

Xcode Build System을 자세히 보면 아래 그림과 같다.

각각의 과정을 자세히 살펴보자 !!

Preprocessor

전처리기는 보통 C언어에서의 #define과 같은 매크로를 실제 정의로 바꾸거나, 파일 간의 종속성 파악, 컴파일 조건문 파악 등의 일을 한다.

하지만 ! 우리의 Swift는 전처리기를 가지고 있지 않다 ! 😲

전처리기가 없기 때문에 원래 Swift에서는 매크로를 사용할 수 없었다.. 근데 Swift 5.9부터 매크로 기능이 도입되었다.. 굉장히 최근에 추가됐죠? 이거에 대해서는 나중에 알아보고 일단은 이 전을 기준으로 전처리기를 알아보자.(왜냐. 이것까지 쓰면 이 글 팔만대장경됨.)

Swift는 전처리기가 없다고 위에서 말했는데, 음.. 난 컴파일 조건문을 사용하는걸 본 적이 있다 ! 손 ~

맞습니다 ~ 쓸 수 있습니다 ! 바로 아래와 같은 코드 !!

#if DEBUG // 디버깅할 때 실행 될 코드
TaskForDebug()
#elseif RELEASE // 릴리즈될 때 실행 될 코드
TaskForRELEASE()
#endif

#if os(iOS) // iOS에서만 할 작업
TaskForiOS()
#elseif os(macOS) // MacOS에서만 할 작업
TaskForMacOS()
#elseif os(watchOS) // WatchOS에서만 할 작업
TaskForWatchOS()
#endif

이게 어떻게 가능한 것일까?

이건 llbuild(low-level build)라는 시스템을 통해 처리되는 것이다. 전통적인 전처리기와는 다른 것이므로 헷갈리지 말아야한다.
이 llbuild(low-level build)는 오픈소스이니.. 궁금하면 들어가서 훑어보셔요..

아 그리고 위에 컴파일 조건문은 Xcode에서 Target → Build Settings → Active Compilation Conditions 에서 설정하고 사용할 수 있는 것이다.

저거 설정 안하고 그냥 돌리면 안됨

Compiler

컴파일러는 위에서 알아본 것과 같이 우리의 소스파일을 저수준 언어로 바꿔주는 일을 한다.
Swift의 컴파일 과정은 아래 그림과 같은 LLVM을 거쳐 실행된다.

저 드래곤이 LLVM 아이콘(?) 이라는데.. 그리기 진짜 어렵겠다..

LLVM은 프로젝트의 이름으로 프로그램의 작성 언어와 관계없이 최적화를 쉽게 구현할 수 있도록 구성되어있는 컴파일러이자 툴킷이다.

우리의 Xcode는 두 개의 컴파일러를 사용한다.

하나는 Swift를 위한 컴파일러인 swiftc이고 나머지 하나는 Objective-C/C++, C/C++ 파일들을 위한 컴파일러 clang이다.

clang도 오픈소스로 들어가서 코드를 뜯어볼 수 있다는 점 ~

그래서 최종적으로 Xcode의 컴파일러는 아래와 같은 형태를 띈다.

Xcode의 컴파일러의 경우, 프론트엔드와 백엔드에서 각각 다른 최적화 과정을 거치게된다.

읭..? 왠.. 프론트 .. 백..?

아래 사진을 보면 더 쉽게 이해할 수 있을 것이다.

Xcode 내부에서 그러니까 iOS나 macOS 프로그램을 만들다보면 우리 프로그램은 C언어와 Swift언어.. 말고도 다른 언어들도 섞여있는 프로그램을 만들게 된다. 그리고 이 각각의 언어들이 자신의 컴파일러를 각각 가지고있지 않은가? 이걸 한 번에 처리 불가능하니까 ! 각각의 컴파일러가 중간언어(LLVM IR)로 바꾸는 과정을 프론트, 중간언어를 어셈블리어로 최종 변환시키는 과정을 백으로 부르는 것이다.

이 중 Swift의 컴파일 과정을 조금 더 자세히 알아보자.

Swift의 컴파일러 내부, 즉 프론트엔드 쪽에서 처음 일어나는 일은, 소스 프로그램이 의미나 유형의 정보 따위 없이 별도의 조각으로 분할되고 AST 구조로 변환되는 것이다. (옛날에..그.. Tokenizer.. 기억이 새록새록..)

이 AST 구조로 변한 Swfit AST를 swiftc가 중간 단계의 표현으로 바꿔주게 된다. swiftc에서 이 중간 단계의 언어를 Swift Intermediate Language(SIL)이라고 한다.

이 SIL은 기계어로 바로 변환될 수 없기 때문에 LLVM Intermediate Representation(LLVM IR)으로 한 번 더 변환된다.

최종적으로 Swift 언어의 컴파일 과정을 정리해보자면 아래와 같다.

  1. 프론트엔드: Swift 소스코드 → Swift AST → Swift IL(SIL) → LLVM IR
  2. LLVM IR → 백엔드
  3. 백엔드(LLVM): LLVM IR → 어셈블리어

Assembler

어셈블러는 이제 사람이 읽을 수 있는 어셈블리어를 기계어로 바꾸게 된다.

이 어셈블러는 기초적인 code와 data로만 이루어진 Mach-O(Mach Object file format) 파일을 만든다.

Mach-O는 목적 파일이나 실행 파일 및 라이브러리에 사용되는 iOS, macOS 운영체제용 특정한 binary 파일 포맷이다.

Linker

링커는 iOS나 macOS 시스템에서 실행 가능한 싱글 Mach-O 파일을 여러 목적 파일이나 라이브러리들과 병합시켜주는 프로그램이다.

어셈블러와 링커는 모두 Mach-O 파일을 반환한다. 그럼 이 둘의 차이점은 무엇일까?

어셈블러를 통해 나온 목적 파일은 아직 미완전한 형태이다. 왜냐하면, 해당 Mach-O파일이 참조하고 있는 다른 목적 파일들 혹은 라이브러리들이 포함되어있지 않은 상태이기 때문이다.

Loader

마지막으로 ! 로더는 OS의 한 부분으로, 프로그램을 메모리에 위치시키고 실행시키는 역할을 한다.

이 로더가 프로그램을 실행시키기 위해 필요한 메모리 영역을 할당하고, 레지스터를 초기 상태로 만들어준다.


원래 Struct와 Class의 메모리 저장 구조에 대해 알아보려다 런타임과 컴파일에 대한 이야기가 나오길래 점점 알아보던게.. 이렇게 빌드 과정까지 와버렸다 😇 그래도 Swift의 컴파일러들도 알게 되고 전반적인 빌드 과정을 쫙 살펴볼 수 있어서 좋았다.

위 과정에 대해 학습하다가 증분빌드와 매크로에 관한 이야기(중간에 Swift5.9부터 나왔다던 그거..)도 알게 되었는데, 다음에 따로 정리를 해봐야겠다.

참고자료

🔗 프로그래밍 언어와 빌드 과정

🔗 Understanding Xcode Build System

🔗 swift의 빌드 과정은 어떻게 되는걸까?

🔗 LLVM이란

🔗 iOS 앱의 빌드 과정

🔗 Swift Compiler #1

저작자표시 (새창열림)

'Swift' 카테고리의 다른 글

[Swift] Task  (8) 2024.10.25
[Swift] Actor(1)  (4) 2024.10.21
[Swift] Property wrapper  (0) 2024.10.06
[Swift] Value Type과 Reference Type  (4) 2024.09.01
'Swift' 카테고리의 다른 글
  • [Swift] Task
  • [Swift] Actor(1)
  • [Swift] Property wrapper
  • [Swift] Value Type과 Reference Type
00me
00me
얼렁뚱땅 방장이 운영하는 기술 블로그
  • 00me
    영미의 iOS 다이어리
    00me
  • 전체
    오늘
    어제
    • 프로그래밍 (29)
      • 📖 (0)
      • CS (4)
      • Python (5)
      • Swift (5)
      • iOS (3)
      • 코테 (3)
        • 자료구조 (0)
        • 알고리즘 (3)
      • 회고 (9)
  • 링크

    • 🍧 GitHub
  • 인기 글

  • hELLO· Designed By정상우.v4.10.3
00me
[Swift] Swift 빌드 과정
상단으로

티스토리툴바