- 빌드 버튼을 누르면 어떻게 될까?
- Xcode 빌드 시스템 -> Xcode 10에서 Swift로 재작성 됨, llbuild라는 이름으로 오픈소스로 공개됨
- 프로젝트 파일을 입력으로 받아서, 의존성 그래프를 구성함
- Objective-C의 경우, 해당 헤더를 참조하고 있는 곳을 기록한 별도의 파일(확장자가 .d)을 만들어놓고, 해당 헤더에 변경이 있으면 관련 부분을 재컴파일 한다.
- 프로젝트 전체를 매번 빌드하면 오래 걸리니가, 증분 빌드를 최대한 유지하는 게 좋다.
- 변경을 감지하기 위해 input의 통계 정보와 메타 데이터를 사용해서 시그니처를 만들고, 이전 빌드와 비교한다.
- 빌드 시스템에서 고려해야 되는 것은 빌드의 순서가 아니라 의존성이다.
- 컴파일러
- 링커
- 프로젝트 빌드 과정에서 수 많은 명령어가 실행됨 -> 빌드는 수많은 명령어 실행을 자동화 한 것
- 빌드 순서는 의존성을 따라 실행된다.
- 의존성의 종류
- 내장(Built in)
- 타겟 의존성: 명시적인 의존성
- 타겟에서 필요한 부분 만큼만 빌드되면 가져다 쓸 수 있도록 개선되었다.
- 다만, Run Script가 있다면 해당 스크립트가 끝나야만 된다.
- 암시적 의존성: 타겟 의존성이 가지는 의존성들
- Build Phase 의존
- 기본적으로는 빌드의 순서를 의미한다.
- 다만 가능하다면 순서가 지켜지지 않기도 한다.
- 만약 순서가 중요한데 잘못된 순서로 Build Phase를 구성하면 빌드가 실패할 수도 있다.
- Scheme Order 의존
- Parallel을 키면 상관 없다.
- 하지만 끄면, Scheme에 써있는 순서대로 빌드한다.
- 개발자 지정 의존성(Run Script)
- input 파일과 output file의 상태에 따라 스크립트의 재실행 여부가 달라진다. -> 즉 스크립트를 순수해야한다.
- 이게 없으면, 매번 실행된다.
- 자동링크를 사용하지 마라
- 빌드 시스템 레벨에서 의존성 그래프에 들어가지 않기 때문에, 링크 이전에 확실히 빌드가 끝났는지를 보장할 수 없다.
- 기본 제공 라이브러리에서만 적용하고, 서드 파티는 최대한 명시적으로 링크하라
- 여러개의 프로젝트를 연결할 거면, 워크스페이스를 만들어라
- 결론: 의존성을 잘 구성해야 빌드가 빨라질 수 있다.
Clang Builds
- Swift만 쓰더라도 그 뒤에서는 Clang이 돌아간다.
- Clang은 Apple의 공식 C계열 언어 컴파일러이다.
- C, C++, Objective-C, Objective-C++을 지원한다.
- Clang의 동작 원리
- input파일과 output파일의 1대1 매치,
- header는 소스파일(구현체)에 대한 약속이다.
- header에서 요구하는 것을 구현하지 않으면, 링크할 때 에러가 난다.
- 소스파일과 헤더파일은 자신이 사용하는 헤더들을 모두 찾아야 한다.
- header map: 헤더의 위치를 동적으로 찾기 위한 방법(.hmap 파일)
- 헤더의 위치가 변경되더라도 소스코드는 변경하지 않기 위한 방법
- 이 덕분에 Clang은 소스 디렉토리에서 에러를 감지할 수 있다.
- 헤더의 이름은 고유해야 하며, 반드시 프로젝트에 포함되어야 한다.
- 시스템 헤더는 어떻게 찾는가?
- 헤더맵은 사용자 지정 해더 정보만 포함한다.
- $(SDKROOT)/usr/include나 $(SDKROOT)/System/Library/Frameworks 도 검색 대상에 포함된다.
- 두번째 디렉토리에서 Foundation을 찾을 수 있다.
- Framework에는 PrivateHeader 폴더가 있다. 애플은 Private Header를 모두 빼고 배포하지만, 사용자 프로젝트나 프레임워크는 이를 포함할 수도 있으니 뒤진다.
- 여기까지 찾아도 없다면 탐색이 실패한 것이다.
- clang module: 여러 헤더파일을 include하는 것에 대한 중복을 해결하기 위한 clang의 방법.
- 다른 해결법으로는 미리 컴파일 된 헤더를 쓰는 방법이 있다.
- clang module은 디스크에 캐시된 해더 표현이다. -> 헤더를 한번만 파싱하고, 이 결과를 저장해 놓는 것
- 재사용 가능하고, 빠른 빌드 타임을 가져갈 수 있다.
- 모듈 특징
- context-free: 지역적인 매크로는 무시된다.
- 지역적인 매크로를 허용하면 재사용이 어려워진다.
- self-contained: 자신이 필요한 의존성을 모두 가져간다. -> 특정 모듈을 import할 때, 해당 모듈의 의존성을 명시적으로 import할 필요가 없다.
- ModuleMap: 모듈의 헤더파일이 사용자 모듈에 어떻게 매핑되는지를 표현한
- umbrella 헤더: 모듈의 public 인터페이스의 일부가 되는 헤더.
- 컴파일러 인자가 달라지면 모듈 캐시도 달라져야 하기 때문에, 컴파일러 인자는 최대한 고치지 말아야 한다.
- 자기 자신의 헤더를 명시적으로 import하기 위해서(자기 자신에게는 module 폴더가 없으니까), Clang’s VFS라는 것을 만들어서, module 폴더가 자기 자신이 되도록 조정해준다.
- 프레임워크 이름을 지정하지 않고 import하게 되면 문제가 생길 여지가 있다.
- 이 부분은 자동으로 고치고 싶어도 고치지 못하는 사태가 생긴다.
#import <PetKit/PetKit.h>
#import "Cat.h" // Cat은 PetKit에 포함된 헤더
// 중복 정의 오류
Swift with Clang Module
- Clang은 header를 이용해 정의를 찾아내는데, swift는 헤더를 쓰지 않는다.
- 초심자가 시작하기 쉽다
- 개별 파일에 정의가 중복되는 것을 막는다
- 컴파일러는 book-keeping 기법으로 선언을 찾아낸다.
- book-keeping과정
- 선언을 찾아낸다. -> 이 과정에서 몸체는 확인하지 않는다.
- 스위프트 타겟 혹은 Objective-C안에서
- 인터페이스를 만들어낸다.
- Objective-C나 다른 swift 타겟에서 사용하기 위해서
- swift는 Clang과 다르게 한 파일을 컴파일하기 위해서는 다른 모든 파일을 컴파일 해야 한다.
- Xcode 9 까지는 선언을 찾기 위해서 각 파일을 반복적으로 파싱했어야 했다.
- 각 파일은 개별적으로 컴파일된다.
- 병렬 빌드를 키면, 점진적으로 컴파일이 완료된다.
- Xcode10 부터는 파일을 여러개의 그룹으로 묶어서 코어에 할당한다.
- 파싱 결과를 프로세스간에 공유한다.
- 프로세스 사이에서만이 반복적인 파싱이 일어나서 좀 더 효율적이다.