윌슨의 개발지식- 개발환경과 빌드(1)
컴파일이란?
작성된 소스 파일들을 특정 플랫폼(런타임)에서 이해할 수 있는 언어(일반적으로 기계어에 가까운 언어를 지칭)로 변환하는 프로세스에요.
플랫폼이란?
플랫폼이라는 용어는 일반적으로 “아키텍쳐“와 그 위에 올라오는 “운영체제” 쌍을 의미합니다. 그러나 최근에는 좀 더 의미가 확장되어 운영체제 위에 “소프트웨어“가 추가된 형태로 얘기되는 것 같아요. 저는 좀 더 일반적으로 생각하기 위해서 “소프트웨어 또는 라이브러리가 실행될 수 있는 환경을 위한 조건“이라는 개념을 가지고 있되, 컨텍스트에 따라서 해석하고는 합니다. 예로 들어, C++환경에서는, “아키텍쳐-OS”로 생각하는 것이죠.
빌드란?
특정 플랫폼의 빌드머신에서, 타겟 플랫폼의 런타임 위에서 동작하는 프로그램이나 사용할 수 있는 플러그인, 프레임워크, 라이브러리, SDK 등을 제작하는 프로세스. 일반적으로 컴파일 과정을 포함해요.
그러면 크로스플랫폼 빌드란?
특정 플랫폼의 빌드머신에서,하나 이상의 다양한 타겟 플랫폼의 런타임 위에서 동작하는 프로그램이나 플러그인, 프레임워크, 라이브러리, SDK 등을 생산하는 프로세스.
런타임
이렇게 프로그램이 실행될 수 있는 환경을 실행환경(이것도 결국 프로그램), 즉 Runtime Environment라고 합니다. 예로, Java는 해당 OS에 설치된 JRE(=Java Runtime Environment)라는 프로그램 위에서 자바의 IR 코드가 컴파일되어 실행되는 것이죠.
그러면 C++ 크로스플랫폼 빌드란?
“특정 플랫폼 빌드머신에서 C++로 작성된 소스를 하나 이상의 다양한 타겟 플랫폼의 런타임 위에 설치하여 동작하는 프로그램이나 플러그인, 프레임워크, 라이브러리, SDK 등을 생산하는 프로세스“라 말할 수 있겠죠.
이에 따르면, 크로스플랫폼 빌드를 위해 다음을 고려해야 합니다.
- 빌드 머신 또는 호스트 시스템의 플랫폼
- 타겟 플랫폼 빌드 툴채인
- 타겟 플랫폼 스탠다드 라이브러리
쉽게 예를 들어 보면, 내가 만약 armeabi-v7a 아키텍쳐, Android OS 위에서 동작하는 C++로 개발된 라이브러리를 빌드하려면, 먼저 어디서 빌드할 것인지를 결정해야 합니다. 만약 Mac OSX로 선정했다면 이 OS에서 타겟 플랫폼인 “armeabi-v7a-android”를 컴파일할 수 있는 빌드 툴채인과 스탠다드 라이브러리를 지원하는지 확인합니다. 지원하면 빌드할 수 있는 거겠죠.
그렇다면, 위에서 살펴본 고려사항에 기반해서 주요 5개 타겟 플랫폼의 빌드환경을 살펴보자.
타겟 플랫폼별 빌드환경

타겟 플랫폼 빌드 FAQ
Windows
-
/MD, /MT 옵션이란?
윈도우에서는 Standard Library가 CRT 라이브러리에 의존적인데, MD는 CRT dll라이브러리를 동적으로 링킹해서 사용하는 옵션이고, MT는 CRT lib라이브러리로 링킹해서 사용하겠다는 것이에요.
MD의 경우, CRT 라이브러리가 최종 바이너리에 포함되지 않기 때문에, 파일 사이즈가 작아집니다. 반면에, 시스템에 CRT dll이 없는 경우 동작하지 않겠죠. 반대로 MT는 라이브러리가 최종 바이너리에 포함되기 때문에 시스템과 상관없이 항상 동작합니다. 반면에 파일 사이즈가 크겠죠.
*주의할 점은 MD옵션으로 빌드한 라이브러리를 사용하려면 마찬가지로 MD옵션으로 라이브러리 또는 실행프로그램을 빌드해야 합니다. (MT도 마찬가지)
-
왜 윈도우는 동적 라이브러리가 .lib와 .dll로 생성되는가?
앞에서 얘기했듯이, 오브젝트 코드는 Symbol Table을 포함하고 있기 때문에, 이를 기반으로 링킹이 진행되죠. 마찬가지로 이 오브젝트 코드가 병합된 형태인 dynamic 라이브러리(또는 shared library) 또한 로드 타임에 이 심볼테이블을 이용해서 링킹이 일어납니다. 이 심볼테이블이 윈도우에서는 .lib파일에 포함된 것입니다. 그리고 .lib파일을 “Import Library”라고 부릅니다. 다른 플랫폼은 윈도우와는 다르게 동적 라이브러리 파일 하나에 심볼 테이블이 포함되어 있다고 합니다.
참고로 Dynamic 라이브러리의 링킹방법은 두 가지가 있고 하나는 load 타임 링킹이고 두 번째는 runtime 링킹입니다. 후자는 심볼 테이블이 필요없이 함수를 다이렉트로 호출하는 것으로 알고 있습니다.
-
Cygwin vs MinGW vs MinGW-w64
윈도우 빌드를 윈도우가 아닌 다른 빌드머신에서 하려고 할 때 가장 혼란을 주는 개념들이 바로 Cygwin, MinGW, MinGW-w64입니다.
MinGW는 “Minimalist GNU for Windows의 약자로 윈도우 환경에서 GNU 툴채인을 이용하기 위한 환경을 의미합니다.” MinGW에는 컴파일러 툴채인 및 플랫폼 헤더와 라이브러리가 포함되어 있습니다. Microsoft에서는 이 플랫폼 라이브러리 소스를 오픈하지 않았기 때문에, MinGW는 플랫폼 라이브러리 헤더인 windows.h를 MSDN에 문서에 기반해서 순수히 구현했다고 하네요. 따라서 불완전하기도 하고 최신 API는 빠져있는 경우가 있다네요. 또한, MSVC로 빌드한 C++라이브러리는 C Runtime Library에 의존적인데 이 또한 오픈되어 있지 않기 때문에 손수 구현했다고 합니다.
MinGW-w64는 MingGW와는 완전히 별개라고 합니다. OneVision이라는 회사에서는 자사 라이브러리를 64bit 윈도우 타겟으로 포팅하기 위해 64비트 윈도우용 툴채인을 직접 제작했다고 합니다. 이렇게 제작된 툴채인을 MinGW에 제출했지만, 코드를 확신할 수 없었던 MinGW에서 코드를 거절했고, 이에 완전히 별개로 MinGW-w64 라는 이름으로 프로젝트를 운영하기 시작했다고 합니다. MinGW가 32비트만 지원하는 반면, MinGW-w64는 64비트 뿐 아니라 32비트도 지원한다고 하네요.
Cygwin은 완전히 타겟이 다른 빌드환경입니다. Cygwin은 윈도우 OS위에 리눅스 에뮬레이팅 레이어를 올리고 그 OS에서 동작하는 라이브러리 또는 실행 프로그램을 빌드합니다. 결국, Cygwin으로 빌드하면 Cygwin이 타겟인 것입니다. 프로그램을 받은 유저 또한 Cygwin이 설치되어 있어야 해당 바이너리를 실행할 수 있습니다.
Android
-
NDK란?
ndk-bundle은 많은 툴채인들이 포함된 도구집합이다. 이 중 ndk-build 프로그램은 Android.mk와 Application.mk를 입력파일로 받아서 여기에 정의된 설정값에 따라 ndk-bundle에 포함되어 있는 clang, gcc, archiver, assembler 등 툴채인들을 선택해서 빌드를 수행한다.
Web Browser와 Web Assembly
-
Emscripten이란?
Emscripten은 LLVM을 이용해서 만든 컴파일러로, Frontend는 그대로 사용하되 Optimizer와 Backend를 웹어셈블리용으로 개발한 컴파일러다. 여기서 Emscripten에 포함된 웹어셈블리용 Optimizer를 Binaryen이라고 부른다. 자세한 컴파일 과정은 아래와 같다. (웹어셈블리에 대해서는 다음에 더 자세히 설명하도록 할게요)
CMake란?
위에서 언급한 C++ 컴파일 프로세스를 고려하면, 특정 타겟 플랫폼에 대해서 소스를 컴파일, 패키지하고, 헤더파일 추가나 소스파일 변경을 감지하는 것이 바로 “빌드시스템”이죠. 이 빌드시스템이 바로 make, ninja고 Visual Studio고, XCode에요. 그리고 빌드시스템을 생성하는 것이 바로 “CMake”입니다.
타겟 플랫폼으로 소스를 빌드하는 시스템을 생성하려면 어떤 입력Input이 필요할까요?
-
- 용하는가?
- 어떤 linker를 사용하는가?
- 컴파일 대상이 되는 소스 파일과 헤더 파일은?
- 빌드 옵션을 어떻게 줄 것인가?
- compiler 옵션을 어떻게 줄 것인가?
- assembler 옵션을 어떻게 줄 것인가?
- linker 옵션을 어떻게 줄 것인가?
- 스탠다드 라이브러리는 무엇을 쓸 것인가?
- 어떤 형태의 라이브러리 또는 실행파일을 빌드할 것인가?
- 어떤 빌드 시스템을 생성할 것인가?
여기에 크로스 컴파일을 위해서 추가적으로 고려해야 하는 점은 다음과 같아요.
- 빌드 툴채인마다 다른 컴파일러, 어셈블러, 링커 옵션들 : 왜냐하면, gcc와 clang, MSVC에 따라 컴파일러 옵션의 컨벤션과 키워드가 다릅니다. *따라서 CMake에서 컴파일 옵션을 문자열로 주면 특정 컴파일러에서만 동작할 리스크가 있습니다. 예로 들어, “-std=c++11” *CMake에서는 컴파일에 상관없이 표준화된 인터페이스를 제공하기 위해 가급적으로 CMake에서 제공되는 feature로 이러한 옵션을 정의하기를 권장하는 추세죠. 다만, 아직까지 완벽하지는 않습니다. 다음은 CMake에서 지원하는 feature 옵션 함수에요.
target_compile_features(myTarget PUBLIC cxx_variadic_templates cxx_nullptr PRIVATE cxx_lambdas )
위와 같은 입력은 다음처럼 CMake의 커맨드 또는 파일에 매핑될 수 있어요.
이렇게 보면 CMake의 각각의 커맨드가 어떤 역할을 하는지 명확하지 않나요?
CMakeLists.txt에서는, add_library로 내가 정한 이름으로 target 객체를 생성합니다. 그리고 이 추가된 타겟 객체에 target_compile_options, target_compile_features, target_include_directories 명령어를 호출하여 컴파일 옵션, 컴파일 기능, 그리고 헤더파일을 추가하죠.
cmake명령어의 옵션 중 DCMAKE_TOOLCHAIN_FILE에 툴채인 파일을 넘깁니다. 마찬가지로 -G 옵션을 추가해서 빌드시스템 타입을 정의합니다. 다만 Make 빌드시스템은 기본값으로 설정되어 있어서 -G 옵션을 줄 필요가 없습니다.
타겟이란?
계속해서 “타겟”이라는 단어를 언급했는데, CMake와 빌드시스템 등을 이해할 때 가장 핵심이 되는 것은 위에서 얘기한 빌드에 대한 이해도와 타겟의 개념이에요.
타겟은 빌드의 최소 단위에요. 그리고 그 빌드의 최소가 되는 단위를 만드는 CMake command는 아래처럼 딱 3개 뿐입니다.
- add_library. 옵션은 OBJECT, STATIC, SHARED
- add_executable
- add_custom_target